Linux网络编程:socket实现client/server通信

您所在的位置:网站首页 socket_addr_in Linux网络编程:socket实现client/server通信

Linux网络编程:socket实现client/server通信

2023-05-20 04:41| 来源: 网络整理| 查看: 265

一、问题引入

阅读UNIX网络编程 卷1:套接字联网API 第3版的前4个章节,觉得有必要对书籍上的源码案例进行复现,并推敲TCP的C/S通信过程。

二、解决过程 2-1 server 代码 #include #include #include #include #include #include #include #include #include #include #define PORT 8887 #define QUEUE 20 #define BUFFER_SIZE 1024 int main() { // 定义sockfd, AF_INET:IPv4协议,SOCK_STREAM:字节流套接字 int server_sockfd = socket(AF_INET, SOCK_STREAM, 0); // 定义sockaddr_in struct sockaddr_in server_sockaddr; server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); // bind,成功返回0,出错返回-1 if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1) { perror("bind failure"); exit(1); } // listen,成功返回0,出错返回-1 if (listen(server_sockfd, QUEUE) == -1) { perror("listen failure"); exit(1); } // 客户端套接字 char buffer[BUFFER_SIZE]; struct sockaddr_in client_addr; socklen_t length = sizeof(client_addr); // 成功返回非负描述字,出错返回-1 // accept()被阻塞,等待客户端连接,一旦客户端和服务器完成TCP三次握手,accept()返回一个已连接描述值 int conn = accept(server_sockfd, (struct sockaddr *)&client_addr, &length); if (conn < 0) { perror("connect failure"); exit(1); } printf("**************************\n"); printf("accept successful !!!\n"); char ipbuf[64] = {0}; printf("client || ip: %s, port: %d\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)), ntohs(client_addr.sin_port)); printf("**************************\n"); while (1) { memset(buffer, 0, sizeof(buffer)); // 接收 int len = recv(conn, buffer, sizeof(buffer), 0); if (strcmp(buffer, "exit") == 0 || strcmp(buffer, "q") == 0) break; if(len > 0) { printf("Recv:%s (%d Byte)\n", buffer, strlen(buffer)); } } close(conn); close(server_sockfd); return 0; } 2-2 client 代码 #include #include #include #include #include #include #include #include #include #include #define PORT 8887 #define BUFFER_SIZE 1024 int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); /// 连接服务器,成功返回0,错误返回-1 if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("connect"); exit(1); } printf("**************************\n"); printf("connect successful !!!\n"); char ipbuf[64] = {0}; printf("server || ip: %s, port: %d\n", inet_ntop(AF_INET, &servaddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)), ntohs(servaddr.sin_port)); printf("now you can send message to server\n"); printf("**************************\n"); char sendbuf[BUFFER_SIZE]; while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) { if(sendbuf[strlen(sendbuf)-1] == '\n') sendbuf[strlen(sendbuf)-1] = '\0'; // 发送 send(sockfd, sendbuf, strlen(sendbuf), 0); if (strcmp(sendbuf, "exit") == 0 || strcmp(sendbuf, "q") == 0) break; memset(sendbuf, 0, sizeof(sendbuf)); } close(sockfd); return 0; } 2-3 编译和运行测试

📌 测试环境:CentOS7.6 x64

编译server.c 和 client.c

gcc server.c -g -std=gnu99 -o server 和 gcc client.c -g -std=gnu99 -o client

运行测试:

📌 server.c仅仅实现对单个客户端的连接请求,接受客户端的请求消息,但不作应答

client:

server:

2-4 socket 流程图

所有客户和服务器都从调用socket() 开始,它返回一个套接字描述符。客户随后调用connect() ,服务器则调用bind() 、listen() 和 accept() ,套接字通常使用标准的close()函数关闭。

2-5 socket API 函数 IPv4套接字地址结构 // struct sockaddr原型声明在如下头文件中 #include // uint8_t 原型在如下头文件中 #include struct sockaddr { uint8_t sa_len; sa_family_t sa_family; /* address family: AF_xxx value */ char sa_data[14]; /* protocol specific address */ }; // sockaddr_in原型声明在如下头文件中 #include // uint8_t 原型在如下头文件中 #include // in_addr_t、in_port_t的原型声明在如下头文件中 #include // sa_family_t、socklen_t的原型声明在如下头文件中 #include struct in_addr { in_addr_t s_addr; /* 32-bit IPv4 address */ /* network byte ordered */ }; struct sockaddr_in { uint8_t sin_len; /* length of structure (16) */ sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* 16-bit TCP or UDP port number */ struct in_addr sin_addr; /* 32-bit IPv4 address */ /* network byte ordered */ char sin_zero[8]; /* unused */ }; socket函数 // socket()原型声明在如下头文件中 #include /* * socket函数的功能是:申请套接字资源 * @param family 指协议族,可选项有如下: * AF_INET:IPv4协议, AF_INET6:IPv6, AF_LOCAL:Unix域协议, AF_ROUTE:路由套接字, AF_KEY:密钥套接字 * @param type 指套接字类型,可选项有如下: * SOCK_STREAM:字节流套接字, SOCK_DGRAM:数据报套接字, SOCK_SEQPACKET:有序分组套接字, SOCK_RAW:原始套接字 * @param protocol 指某个协议类型常值,一般默认设置为0。可选项有如下: * IPPROTO_TCP:TCP传输协议, IPPROTO_UDP:UDP传输协议, IPPROTO_SCTP:SCTP传输协议 * @return 若成功返回非负值,否者返回-1。返回值称之为套接字描述符(监听套接字) */ int socket(int family, int type, int protocol); connect函数

📌 客户端调用connect函数将激发TCP的三次握手

// connect()原型声明在如下头文件中 #include /* * connect函数的功能是:与TCP服务器建立连接 * @param sockfd 使用socket函数获取的套接字描述符 * @param *servaddr 一个指向套接字地址结构的指针 * @param addrlen 套接字结构的大小 * @return 若成功则返回0,出错返回-1 */ int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); bind函数

📌 服务器在启动时,利用bind函数捆绑一个固定众所周知的端口号,但对于客户端不需要,可以让内核去分配一个临时端口号

// bind()原型声明在如下头文件中 #include /* * * @param sockfd 使用socket函数获取的套接字描述符 * @param *myaddr 一个指向特定协议的地址结构的指针 * @param addrlen 特定协议的结构的大小 * @return 若成功则返回0,出错返回-1 */ int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); listen函数

📌 listen函数只能由TCP服务器调用

// listen()原型声明在如下头文件中 #include /* * listen函数把一个未连接的套接字转换成一个被动套接字 * @param sockfd 使用socket函数获取的套接字描述符 * @param backlog 套接字排队的最大连接个数 * @return 若成功则返回0,出错返回-1 */ int listen(int sockfd, int backlog); accept函数

📌 accept函数只能由TCP服务器调用,返回一个已连接套接字。若有客户端发起连接请求,并完成三次握手,accept函数被触发

// accept()原型声明在如下头文件中 #include /* * * @param sockfd 使用socket函数获取的套接字描述符 * @param *cliaddr 一个指向特定协议的地址结构的指针 * @param *addrlen 特定协议的结构的大小 * @return 若成功则返回非负描述符,出错则返回-1。返回值称之为:已连接套接字 */ int accpet(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); recv函数和send函数 // ssize_t, size_t 数据原型声明在如下头文件中 #include // recv(),send()原型声明在如下头文件中 #include /* * * @param sockfd 使用socket函数获取的套接字描述符 * @param *buf 用于接受数据的缓冲区 * @param *len 缓冲区的长度 * #param flags 指定可选的标志,用于控制接收行为,一般默认为0 * @return 若返回值大于0,成功返回接收到的字节数; * 若返回值小于0,失败,可读取错误码errno * 若返回值等于0,失败,网络中断 */ ssize_t recv(int sockfd, void *buf, size_t len, int flags); /* * * @param s 使用socket函数获取的套接字描述符 * @param *msg 用于发送数据的缓冲区 * @param *len 缓冲区的长度 * #param flags 指定可选的标志,用于控制接收行为,一般默认为0 * @return 若返回值大于0,成功返回发送的字节数; * 若返回值小于0,失败,可读取错误码errno * 若返回值等于0,失败,网络中断 */ int send(int s, const void *msg, size_t len, int flags); 三、反思总结

本随笔案例仅实现了TCP服务器针对单个客户端的应答,但实际中TCP服务器要为成千上万个客户端服务的。

文中的重点介绍Linux Socket的API,但关于TCP/IP才是Socket的基石。

四、参考引用

UNIX网络编程 卷1:套接字联网API 第3版



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3