Linux网络编程:socket实现client/server通信 |
您所在的位置:网站首页 › socket_addr_in › Linux网络编程:socket实现client/server通信 |
一、问题引入
阅读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: 所有客户和服务器都从调用socket() 开始,它返回一个套接字描述符。客户随后调用connect() ,服务器则调用bind() 、listen() 和 accept() ,套接字通常使用标准的close()函数关闭。 📌 客户端调用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 |