什么是网络编程?
本部分主要是介绍socket网络编程的基本API——并展示一个服务器与客户端连接的具体流程是如何的实现一个一对一的网络服务器程序
要对网络编程进行一个较为深入的学习,还是要费不少笔墨的,具体的详细内容可以查看博主以前写过的一个专栏进行学习:socket编程或者网络编程。相信看完博主之前的介绍大家会对网络编程有一定的认识,所以我们就紧接着之前知识点继续介绍,之前时实现了一个回声服务器,现在将其升级为更高效的服务器。
网络编程效果演示
本部分要演示的效果如下:
演示什么是阻塞什么是非阻塞——socket设置非阻塞的差别演示为何要加while——反复读与读一次
演示多个client连接一个server一个接口发数据,其他client会被卡主的情况演示一请求一线程的情况
演示server不close的状态——不停地打印信息
阻塞与非阻塞的区别
下面将用代码来展示在socket编程中阻塞与非阻塞的区别。
阻塞状态一个server对应一个client
#include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define BUFFER_LENGTH 1024 int main() { // 创建socket int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 清空 servaddr.sin_family = AF_INET; // 设置协议族为 IPV4 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0 servaddr.sin_port = htons(9999); // 监听端口为 9999 // 绑定好初始化的信息,出错就打印出错信息 if (-1 == bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr))) { printf("bind failed: %s", strerror(errno)); return -1; } // 监听绑定好的信息 listen(sockfd, 10); // 为了保存 client的信息锁创建的结构 struct sockaddr_in clientaddr; socklen_t len = sizeof(clientaddr); // 此时的 clientfd就是用来和 client通信的socket int clientfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len); printf("accept,the clientfd is %d\n",clientfd); char buffer[BUFFER_LENGTH] = {0}; int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0); // 接收client发来的信息 printf("ret: %d, buffer: %s\n", ret, buffer); // 本地打印client send(clientfd, buffer, ret, 0); // 将信息传给client return 0; }
运行结果
连接之前
可以看到当没有client连接到server端的时候,server程序会自动阻塞起来不进行输出。
点击连接之后,并发送信息
可以看到当client连接到server之后,server端会打印连接的信息,并将连接的信息返回给client端,并结束程序,自动关闭程序。
非阻塞状态一个server对应一个client
此时不会等待accept连接 核心代码
// 将socket设置为非阻塞 int flags = fcntl(sockfd, F_GETFL, 0); flags |= O_NONBLOCK; fcntl(sockfd, F_SETFL, flags);
#include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <fcntl.h> #include <pthread.h> #include <sys/poll.h> #include <sys/epoll.h> #define BUFFER_LENGTH 1024 #define POLL_SIZE 1024 int main() { // 创建socket int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 清空 servaddr.sin_family = AF_INET; // 设置协议族为 IPV4 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0 servaddr.sin_port = htons(9999); // 监听端口为 9999 // 绑定好初始化的信息,出错就打印出错信息 if (-1 == bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr))) { printf("bind failed: %s", strerror(errno)); return -1; } // 监听绑定好的信息 listen(sockfd, 10); // 将socket设置为非阻塞 int flags = fcntl(sockfd, F_GETFL, 0); flags |= O_NONBLOCK; fcntl(sockfd, F_SETFL, flags); // 为了保存 client的信息锁创建的结构 struct sockaddr_in clientaddr; socklen_t len = sizeof(clientaddr); // 此时的 clientfd就是用来和 client通信的socket int clientfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len); printf("accept,the clientfd is %d\n",clientfd); char buffer[BUFFER_LENGTH] = {0}; int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0); // 接收client发来的信息 printf("ret: %d, buffer: %s\n", ret, buffer); // 本地打印client send(clientfd, buffer, ret, 0); // 将信息传给client getchar(); //block return 0; }
运行结果
可以看到此时还没有对服务器进行连接此时的服务器就已经输出内容了,这就是设置socket阻塞与非阻塞的差别之处。
此时点击连接,然后client再向server输出信息也是没有任何的用处了。
为了后续的方便信息展示,后面的程序都将设置socket非阻塞进行注释。
为什么要使用while循环来反复读取数据
可以看到上述两个程序在第一次读取信息之后(不论成功),server端就断开整个程序的连接了,正常的服务器程序肯定不可能只接收client的一条信息吧,所以我们需要增加一个while循环来一直进行读取数据,这也就是为什么服务器是7*24小时运行的关键之处
#include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <fcntl.h> #include <pthread.h> #include <sys/poll.h> #include <sys/epoll.h> #define BUFFER_LENGTH 1024 #define POLL_SIZE 1024 int main() { // 创建socket int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 清空 servaddr.sin_family = AF_INET; // 设置协议族为 IPV4 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0 servaddr.sin_port = htons(9999); // 监听端口为 9999 // 绑定好初始化的信息,出错就打印出错信息 if (-1 == bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr))) { printf("bind failed: %s", strerror(errno)); return -1; } // 监听绑定好的信息 listen(sockfd, 10); // 将socket设置为非阻塞 int flags = fcntl(sockfd, F_GETFL, 0); flags |= O_NONBLOCK; fcntl(sockfd, F_SETFL, flags); // 为了保存 client的信息锁创建的结构 struct sockaddr_in clientaddr; socklen_t len = sizeof(clientaddr); // 此时的 clientfd就是用来和 client通信的socket int clientfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len); printf("accept,the clientfd is %d\n", clientfd); while (1) { char buffer[BUFFER_LENGTH] = {0}; int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0); // 接收client发来的信息 printf("ret: %d, buffer: %s\n", ret, buffer); // 本地打印client send(clientfd, buffer, ret, 0); // 将信息传给client } getchar(); // block return 0; }
运行结果
运行之前
此时是阻塞模式需要等待client连接到server才会打印信息。
连接之后
可以看到此时可以随意的对此程序进行读写操作
server不写close函数,当client调用close时server端会发生的情况
依旧是这一套代码,此时会发现server端会疯狂打印如下的信息,要解决此问题很简单,只需要在recv返回值为0(此时代表client主动关闭连接)时server在调用close函数即可。
多个client连接一个server
依旧使用这个程序,使用两个client连接server,此时可以发现只有一个client可以发数据,其余client发数据会被卡住
要想解决这个问题,就需要在while循环之中增加accept函数,但是此时又会出现一个问题,每次循环都会卡在accept之中,这就很值得思考了。
accept放在while循环中导致每次读写被accept阻塞解决思路
此时就需要用到多线程了。server每接收到一个client的连接就可以创建一个线程去对哪一个client进行读写操作。
#include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <fcntl.h> #include <pthread.h> #include <sys/poll.h> #include <sys/epoll.h> #define BUFFER_LENGTH 1024 #define POLL_SIZE 1024 // 1 connection 1 thread void *client_thread(void *arg) { int clientfd = *(int *)arg; while (1) { // slave char buffer[BUFFER_LENGTH] = {0}; int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0); if (ret == 0) { close(clientfd); break; } printf("ret: %d, buffer: %s\n", ret, buffer); send(clientfd, buffer, ret, 0); } } int main() { // 创建socket int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 清空 servaddr.sin_family = AF_INET; // 设置协议族为 IPV4 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0 servaddr.sin_port = htons(9999); // 监听端口为 9999 // 绑定好初始化的信息,出错就打印出错信息 if (-1 == bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr))) { printf("bind failed: %s", strerror(errno)); return -1; } // 监听绑定好的信息 listen(sockfd, 10); #if 0 // 将socket设置为非阻塞 int flags = fcntl(sockfd, F_GETFL, 0); flags |= O_NONBLOCK; fcntl(sockfd, F_SETFL, flags); #endif // 为了保存 client的信息锁创建的结构 struct sockaddr_in clientaddr; socklen_t len = sizeof(clientaddr); while (1) { // master int clientfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len); #if 0 char buffer[BUFFER_LENGTH] = {0}; int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0); printf("ret: %d, buffer: %s\n", ret, buffer); send(clientfd, buffer, ret, 0); #else pthread_t threadid; pthread_create(&threadid, NULL, client_thread, &clientfd); #endif } getchar(); // block return 0; }
此时可以看到连续三个client连接server,随意进行通信都能够完成正常的通信。
网络编程与select/poll/epoll服务器的实现(2):https://developer.aliyun.com/article/1415913?spm=a2c6h.13148508.setting.18.16254f0exsfwiz