Linux下的C++ socket编程实例
阅读目录
- 基本的局域网聊天
- 客户端服务端双向异步聊天源码
- 局域网内服务端和有限个客户端聊天源码
- 完美异步聊天服务端和客户端源码
- C++定时器
- select异步代码
- pthead多线程
服务端:
服务器端先初始化socket,然后与端口绑定,对端口进行监听,调用accept阻塞,等待客户端连接。
socket() -> bind() -> listen() -> accept()
客户端:
客户端先初始化socket,然后与服务端连接,服务端监听成功则连接建立完成
socket() -> connect()
socket的大概过程是这样的:
服务端先创建一个套接字,端口绑定,对端口进行监听,调用accpet阻塞,等待客户端连接。客户端创建一个套接字,然后通过三次握手完成tcp连接后服务端accpet返回重新建立一个套接字代表返回客户端的tcp连接,(在accpet成功返回前有一个要注意的是server会有两个队列,一个存放完成三次握手的一个是未完成三次握手的,每次accpet会从完成三次握手的队列中取出一个并一直建立TCP连接,此时才能算是真正的连接成功),完成上面的步骤后即可以开始数据的传输了,数据传输结束后再调用close关闭连接
此外再说一下select函数在server和client双向通信中的重要作用:网络编程的过程中,经常会遇到许多阻塞的函数,网络编程时使用的recv, recvfrom、connect函数都是阻塞的函数,当函数不能成功执行的时候,程序就会一直阻塞在这里,无法执行下面的代码。selcet函数是一个轮循函数,即当循环询问文件节点,可设置超时时间,超时时间到了就跳过代码继续往下执行,就像我们下面的第一个程序一样,如果不注释掉server的send那么如果server不想client发送消息则进程就会停顿在此处等待server发送无法执行下面的代码,无法接受client发送过来的消息,第二个程序就对此进行的改进,在程序中引入了select当超时后就会跳过当前代码,执行下一步不会一直阻塞。(poll和epoll是对select的改进)
TCP编程的服务器端一般步骤是: | UDP编程的服务器端一般步骤是: |
---|---|
1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt(); * 可选 3、绑定IP地址、端口等信息到socket上,用函数bind(); 4、开启监听,用函数listen(); 5、接收客户端上来的连接,用函数accept(); 6、收发数据,用函数send()和recv(),或者read()和write(); 7、关闭网络连接; 8、关闭监听; |
1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt();* 可选 3、绑定IP地址、端口等信息到socket上,用函数bind(); 4、循环接收数据,用函数recvfrom(); 5、关闭网络连接; |
TCP编程的客户端一般步骤是: | UDP编程的客户端一般步骤是: |
1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt();* 可选 3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 4、设置要连接的对方的IP地址和端口等属性; 5、连接服务器,用函数connect(); 6、收发数据,用函数send()和recv(),或者read()和write(); 7、关闭网络连接; |
1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt();* 可选 3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 4、设置对方的IP地址和端口等属性; 5、发送数据,用函数sendto(); 6、关闭网络连接; |
基本的局域网聊天
局域网TCP服务端:
实现的功能是client到server的半双工通信,server只能接受接收client发送过来的消息,但是不能向client发送消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #include <thread> #include <iostream> #define PORT 7000 #define QUEUE 20//连接请求队列 int conn; void thread_task() { } int main() { //printf("%d\n",AF_INET);//IPv4协议 printf ( "%d\n" ,SOCK_STREAM); //字节流套接字 int ss = socket(AF_INET, SOCK_STREAM, 0); //若成功则返回一个sockfd(套接字描述符) //printf("%d\n",ss); struct sockaddr_in server_sockaddr; //一般是储存地址和端口的。用于信息的显示及存储使用 /*设置 sockaddr_in 结构体中相关参数*/ server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); //将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian) //printf("%d\n",INADDR_ANY); //INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 //一般来说,在各个系统中均定义成为0值。 server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); //将主机的无符号长整形数转换成网络字节顺序。 if (bind(ss, ( struct sockaddr* ) &server_sockaddr, sizeof (server_sockaddr))==-1) { perror ( "bind" ); exit (1); } if (listen(ss, QUEUE) == -1) { perror ( "listen" ); exit (1); } struct sockaddr_in client_addr; socklen_t length = sizeof (client_addr); ///成功返回非负描述字,出错返回-1 conn = accept(ss, ( struct sockaddr*)&client_addr, &length); //如果accpet成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接。 //accpet之后就会用新的套接字conn if ( conn < 0 ) { perror ( "connect" ); exit (1); } char buffer[1024]; //创建另外一个线程 //std::thread t(thread_task); //t.join(); //char buf[1024]; //主线程 while (1) { //这里把send注释掉了,所以这个程序中server只能是接收client端的数据并能给client发送数据,即使不注释掉也没用,因为没有对是否有数据传入和传入 //进行判断所以按照下面的代码这样写,每次都要先让server输入后才能输出client传过来的数据,若是server不输入则程序无法向下走就没有client发送过来的输出, //而且每次显示也只能是一行,这样显示就全是错的了,所以就需要select和FD_ISSET的判断了 // memset(buf, 0 ,sizeof(buf)); // if(fgets(buf, sizeof(buf),stdin) != NULL) { // send(conn, buf, sizeof(buf), 0); // } memset (buffer, 0 , sizeof (buffer)); int len = recv(conn, buffer, sizeof (buffer), 0); //从TCP连接的另一端接收数据。 /*该函数的第一个参数指定接收端套接字描述符; 第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据; 第三个参数指明buf的长度; 第四个参数一般置0*/ if ( strcmp (buffer, "exit\n" ) == 0) //如果没有收到TCP另一端发来的数据则跳出循环不输出 { break ; } printf ( "%s" , buffer); //如果有收到数据则输出数据 //必须要有返回数据, 这样才算一个完整的请求 send(conn, buffer, len , 0); //向TCP连接的另一端发送数据。 } close(conn); //因为accpet函数连接成功后还会生成一个新的套接字描述符,结束后也需要关闭 close(ss); //关闭socket套接字描述符 return 0; } |
局域网TCP客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | /*局域网TCP客户端*/ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #define MYPORT 7000 #define BUFFER_SIZE 1024 int main() { ///定义sockfd int sock_cli = socket(AF_INET,SOCK_STREAM, 0); ///定义sockaddr_in struct sockaddr_in servaddr; memset (&servaddr, 0, sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MYPORT); //服务器端口 servaddr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); //服务器ip,inet_addr用于IPv4的IP转换(十进制转换为二进制) //127.0.0.1是本地预留地址 //连接服务器,成功返回0,错误返回-1 if (connect(sock_cli, ( struct sockaddr *)&servaddr, sizeof (servaddr)) < 0) { perror ( "connect" ); exit (1); } char sendbuf[BUFFER_SIZE]; char recvbuf[BUFFER_SIZE]; while ( fgets (sendbuf, sizeof (sendbuf), stdin) != NULL) { /*每次读取一行,读取的数据保存在buf指向的字符数组中,成功,则返回第一个参数buf;*/ send(sock_cli, sendbuf, strlen (sendbuf),0); ///发送 if ( strcmp (sendbuf, "exit\n" )==0) break ; recv(sock_cli, recvbuf, sizeof (recvbuf),0); ///接收 fputs (recvbuf, stdout); memset (sendbuf, 0, sizeof (sendbuf)); //接受或者发送完毕后把数组中的数据全部清空(置0) memset (recvbuf, 0, sizeof (recvbuf)); } close(sock_cli); return 0; } /*在TCP三次握手完成后会进入等待连接队列,等待服务端调用accpet与之建立连接,这时候是server端调用accept跟客户端建立 通信,客户端并不需要调用accpet,因为有很多个客户端要跟服务端建立连接,这时候服务端就会有一个队列,对已经经过三次握 手的才可以建立连接(类似缓存信息),这个是由服务端来确认的,客户端并不知道什么时候服务端才能跟它建立连接,在服务端 没有调用accept与之连接或者还未排队到它,只能是一直等待,直到服务端准备好了才能跟客户端建立连接,所以主动权在服务端*/ |
客户端服务端双向异步聊天源码
以上的局域网聊天应用有一个很重要的缺点, 服务器只能显示客户端发送的消息, 却无法给客户端发送消息, 这个很尴尬;
通过使用C中的select()函数, 实现一个异步聊天工具:
异步聊天服务端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #include <iostream> #define PORT 7000 #define QUEUE 20 int main() { fd_set rfds; struct timeval tv; int retval, maxfd; int ss = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_sockaddr; server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); //printf("%d\n",INADDR_ANY); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(ss, ( struct sockaddr* ) &server_sockaddr, sizeof (server_sockaddr))==-1) { perror ( "bind" ); exit (1); } if (listen(ss, QUEUE) == -1) { perror ( "listen" ); exit (1); } struct sockaddr_in client_addr; socklen_t length = sizeof (client_addr); ///成功返回非负描述字,出错返回-1 int conn = accept(ss, ( struct sockaddr*)&client_addr, &length); /*没有用来存储accpet返回的套接字的数组,所以只能实现server和单个client双向通信*/ if ( conn < 0 ) { perror ( "connect" ); exit (1); } while (1) { /*把可读文件描述符的集合清空*/ FD_ZERO(&rfds); /*把标准输入的文件描述符加入到集合中*/ FD_SET(0, &rfds); maxfd = 0; /*把当前连接的文件描述符加入到集合中*/ FD_SET(conn, &rfds); /*找出文件描述符集合中最大的文件描述符*/ if (maxfd < conn) maxfd = conn; /*设置超时时间*/ tv.tv_sec = 5; //设置倒计时 tv.tv_usec = 0; /*等待聊天*/ retval = select(maxfd+1, &rfds, NULL, NULL, &tv); if (retval == -1) { printf ( "select出错,客户端程序退出\n" ); break ; } else if (retval == 0) { printf ( "服务端没有任何输入信息,并且客户端也没有信息到来,waiting...\n" ); continue ; } else { /*客户端发来了消息*/ if (FD_ISSET(conn,&rfds)) { char buffer[1024]; memset (buffer, 0 , sizeof (buffer)); int len = recv(conn, buffer, sizeof (buffer), 0); if ( strcmp (buffer, "exit\n" ) == 0) break ; printf ( "%s" , buffer); //send(conn, buffer, len , 0);把数据回发给客户端 } /*用户输入信息了,开始处理信息并发送*/ if (FD_ISSET(0, &rfds)) { char buf[1024]; fgets (buf, sizeof (buf), stdin); //printf("you are send %s", buf); send(conn, buf, sizeof (buf), 0); } } } close(conn); close(ss); return 0; } |
异步聊天客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #define MYPORT 7000 #define BUFFER_SIZE 1024 int main() { int sock_cli; fd_set rfds; struct timeval tv; int retval, maxfd; ///定义sockfd sock_cli = socket(AF_INET,SOCK_STREAM, 0); ///定义sockaddr_in struct sockaddr_in servaddr; memset (&servaddr, 0, sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MYPORT); ///服务器端口 servaddr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); ///服务器ip //连接服务器,成功返回0,错误返回-1 if (connect(sock_cli, ( struct sockaddr *)&servaddr, sizeof (servaddr)) < 0) { perror ( "connect" ); exit (1); } while (1){ /*把可读文件描述符的集合清空*/ FD_ZERO(&rfds); /*把标准输入的文件描述符加入到集合中*/ FD_SET(0, &rfds); maxfd = 0; /*把当前连接的文件描述符加入到集合中*/ FD_SET(sock_cli, &rfds); /*找出文件描述符集合中最大的文件描述符*/ if (maxfd < sock_cli) maxfd = sock_cli; /*设置超时时间*/ tv.tv_sec = 5; tv.tv_usec = 0; /*等待聊天*/ retval = select(maxfd+1, &rfds, NULL, NULL, &tv); if (retval == -1) { printf ( "select出错,客户端程序退出\n" ); break ; } else if (retval == 0) { printf ( "客户端没有任何输入信息,并且服务器也没有信息到来,waiting...\n" ); continue ; } else { /*服务器发来了消息*/ if (FD_ISSET(sock_cli,&rfds)) { char recvbuf[BUFFER_SIZE]; int len; len = recv(sock_cli, recvbuf, sizeof (recvbuf),0); printf ( "%s" , recvbuf); memset (recvbuf, 0, sizeof (recvbuf)); } /*用户输入信息了,开始处理信息并发送*/ if (FD_ISSET(0, &rfds)) { char sendbuf[BUFFER_SIZE]; fgets (sendbuf, sizeof (sendbuf), stdin); send(sock_cli, sendbuf, strlen (sendbuf),0); //发送 memset (sendbuf, 0, sizeof (sendbuf)); } } } close(sock_cli); return 0; } |
局域网内服务端和有限个客户端聊天源码
以上的局域网聊天只能支持一个用户, 我们还要改改, 必须是支持多用户的聊天室:
局域网TCP2人聊天服务端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #include <iostream> #include <thread> #define PORT 7000 #define QUEUE 20 int ss; struct sockaddr_in client_addr; socklen_t length = sizeof (client_addr); int conns[2] = {}; //定义了一个容量为2的数组来存放套接字,所以server最多只能跟2个client通信 int z = 0; void thread_fn() { //成功返回非负描述字,出错返回-1 int conn = accept(ss, ( struct sockaddr*)&client_addr, &length); if ( conn < 0 ) { perror ( "connect" ); exit (1); } //把连接保存到临时数组中; conns[z] = conn; z++; fd_set rfds; struct timeval tv; //linux编程中,如果用到计时,可以用struct timeval获取系统时间 int retval, maxfd; while (1) { /*把可读文件描述符的集合清空*/ FD_ZERO(&rfds); /*把标准输入的文件描述符加入到集合中*/ FD_SET(0, &rfds); maxfd = 0; /*把当前连接的文件描述符加入到集合中*/ FD_SET(conn, &rfds); /*找出文件描述符集合中最大的文件描述符*/ if (maxfd < conn) { maxfd = conn; } /*设置超时时间*/ tv.tv_sec = 5; //5秒 tv.tv_usec = 0; /*等待聊天*/ retval = select(maxfd+1, &rfds, NULL, NULL, &tv); if (retval == -1) { printf ( "select出错,客户端程序退出\n" ); break ; } else if (retval == 0) { printf ( "服务端没有任何输入信息,并且客户端也没有信息到来,waiting...\n" ); continue ; } else { /*客户端发来了消息*/ if (FD_ISSET(conn,&rfds)) //判断conn是否在rfds中如果在返回非零,不再返回0 { char buffer[1024]; memset (buffer, 0 , sizeof (buffer)); //把buffer中的所有值赋值为0,即清空buffer int len = recv(conn, buffer, sizeof (buffer), 0); //把接收到的数据存放于buffer中 if ( strcmp (buffer, "exit\n" ) == 0) //如果接受到的是空的,即没有收到任何信息 break ; printf ( "%s" , buffer); //send(conn, buffer, len , 0);把数据回发给客户端 } /*用户输入信息了,开始处理信息并发送*/ if (FD_ISSET(0, &rfds)) { char buf[1024]; fgets (buf, sizeof (buf), stdin); //每次读取一行数据存放在buf中 //printf("you are send %s", buf); for ( int i=0; i<z; i++) { send(conns[i], buf, sizeof (buf), 0); } } } } close(conn); } void thread_select( int conn) { } int main() { ss = socket(AF_INET, SOCK_STREAM, 0); //SOCK_STREAM即tcp协议,AF_INET是IPv4套接字 struct sockaddr_in server_sockaddr; server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); //printf("%d\n",INADDR_ANY); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(ss, ( struct sockaddr* ) &server_sockaddr, sizeof (server_sockaddr))==-1) { perror ( "bind" ); exit (1); } if (listen(ss, QUEUE) == -1) { perror ( "listen" ); exit (1); } std:: thread t(thread_fn); //因为创建了两个线程所以只能连接两个client std:: thread t1(thread_fn); //这里把收发数据都存放在thread_fn中,所以创建一个这样的线程就能使得server能多连接一个server t.join(); t1.join(); close(ss); return 0; } |
局域网TCP2人聊天客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #define MYPORT 7000 #define BUFFER_SIZE 1024 int main() { int sock_cli; fd_set rfds; struct timeval tv; int retval, maxfd; ///定义sockfd sock_cli = socket(AF_INET,SOCK_STREAM, 0); ///定义sockaddr_in struct sockaddr_in servaddr; memset (&servaddr, 0, sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MYPORT); ///服务器端口 servaddr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); ///服务器ip //连接服务器,成功返回0,错误返回-1 if (connect(sock_cli, ( struct sockaddr *)&servaddr, sizeof (servaddr)) < 0) { perror ( "connect" ); exit (1); } while (1){ /*把可读文件描述符的集合清空*/ FD_ZERO(&rfds); /*把标准输入的文件描述符加入到集合中*/ FD_SET(0, &rfds); maxfd = 0; /*把当前连接的文件描述符加入到集合中*/ FD_SET(sock_cli, &rfds); /*找出文件描述符集合中最大的文件描述符*/ if (maxfd < sock_cli) maxfd = sock_cli; /*设置超时时间*/ tv.tv_sec = 5; tv.tv_usec = 0; /*等待聊天*/ retval = select(maxfd+1, &rfds, NULL, NULL, &tv); if (retval == -1) { printf ( "select出错,客户端程序退出\n" ); break ; } else if (retval == 0) { printf ( "客户端没有任何输入信息,并且服务器也没有信息到来,waiting...\n" ); continue ; } else { /*服务器发来了消息*/ if (FD_ISSET(sock_cli,&rfds)) { char recvbuf[BUFFER_SIZE]; int len; len = recv(sock_cli, recvbuf, sizeof (recvbuf),0); printf ( "%s" , recvbuf); memset (recvbuf, 0, sizeof (recvbuf)); } /*用户输入信息了,开始处理信息并发送*/ if (FD_ISSET(0, &rfds)) { char sendbuf[BUFFER_SIZE]; fgets (sendbuf, sizeof (sendbuf), stdin); send(sock_cli, sendbuf, strlen (sendbuf),0); //发送 memset (sendbuf, 0, sizeof (sendbuf)); } } } close(sock_cli); return 0; } |
完美异步聊天服务端和客户端源码
以上的多客户聊天不是很好, 因为只允许两个客户端连接, 体验非常差, 如果支持无限个客户端聊天的话那该多好啊, 哈哈, 这个也是可以的, 我们只要使用c++的list即可, 它是可以自增的数组(其实算是链表), 引用 头文件<list>即可:
无限个客户聊天的 服务端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #include <iostream> #include <thread> #include <list> #define PORT 7000 #define IP "127.0.0.1" int s; struct sockaddr_in servaddr; socklen_t len; std::list< int > li; //用list来存放套接字,没有限制套接字的容量就可以实现一个server跟若干个client通信 void getConn() { while (1) { int conn = accept(s, ( struct sockaddr*)&servaddr, &len); li.push_back(conn); printf ( "%d\n" , conn); } } void getData() { struct timeval tv; tv.tv_sec = 10; //设置倒计时时间 tv.tv_usec = 0; while (1) { std::list< int >::iterator it; for (it=li.begin(); it!=li.end(); ++it) { fd_set rfds; FD_ZERO(&rfds); int maxfd = 0; int retval = 0; FD_SET(*it, &rfds); if (maxfd < *it) { maxfd = *it; } retval = select(maxfd+1, &rfds, NULL, NULL, &tv); if (retval == -1) { printf ( "select error\n" ); } else if (retval == 0) { //printf("not message\n"); } else { char buf[1024]; memset (buf, 0 , sizeof (buf)); int len = recv(*it, buf, sizeof (buf), 0); printf ( "%s" , buf); } } sleep(1); } } void sendMess() { while (1) { char buf[1024]; fgets (buf, sizeof (buf), stdin); //printf("you are send %s", buf); std::list< int >::iterator it; for (it=li.begin(); it!=li.end(); ++it) { send(*it, buf, sizeof (buf), 0); } } } int main() { //new socket s = socket(AF_INET, SOCK_STREAM, 0); memset (&servaddr, 0, sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); servaddr.sin_addr.s_addr = inet_addr(IP); if (bind(s, ( struct sockaddr* ) &servaddr, sizeof (servaddr))==-1) { perror ( "bind" ); exit (1); } if (listen(s, 20) == -1) { perror ( "listen" ); exit (1); } len = sizeof (servaddr); //thread : while ==>> accpet std:: thread t(getConn); t.detach(); //detach的话后面的线程不同等前面的进程完成后才能进行,如果这里改为join则前面的线程无法判断结束,就会 //一直等待,导致后面的线程无法进行就无法实现操作 //printf("done\n"); //thread : input ==>> send std:: thread t1(sendMess); t1.detach(); //thread : recv ==>> show std:: thread t2(getData); t2.detach(); while (1) //做一个死循环使得主线程不会提前退出 { } return 0; } /*这个跟前面的不一样的地方是,把获得连接套接字getConn和发送信息sendMess和接收信息getData放在三个函数中,创建 的三个线程分别对应处理三个函数,就可以使得server能跟若干个client通信*/ |
问:为什么要创建三个线程去处理三个函数,单个线程并不可以吗,多线程和单线程处理起来有什么不同?
答:首先,这里用到多线程的目的是为了提高处理能力,减少等待时间,多线程可以并发执行,即可以同时对三个函数进行处理,处理起来会快很多。这里也是可以用单线程来处理的,但是单线程每次只能做一件事情,不能同时去获得连接套接字、发送消息、接收消息,这样在做其中一件事情的时候其他的两件事情就要等待,这样处理时间会比多线程慢很多。多线程可以及时的响应,单线程不能及时响应。
无限个客户端连接的客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #define MYPORT 7000 #define BUFFER_SIZE 1024 int main() { int sock_cli; fd_set rfds; struct timeval tv; int retval, maxfd; ///定义sockfd sock_cli = socket(AF_INET,SOCK_STREAM, 0); ///定义sockaddr_in struct sockaddr_in servaddr; memset (&servaddr, 0, sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MYPORT); ///服务器端口 servaddr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); ///服务器ip //连接服务器,成功返回0,错误返回-1 if (connect(sock_cli, ( struct sockaddr *)&servaddr, sizeof (servaddr)) < 0) { perror ( "connect" ); exit (1); } while (1){ /*把可读文件描述符的集合清空*/ FD_ZERO(&rfds); /*把标准输入的文件描述符加入到集合中*/ FD_SET(0, &rfds); maxfd = 0; /*把当前连接的文件描述符加入到集合中*/ FD_SET(sock_cli, &rfds); /*找出文件描述符集合中最大的文件描述符*/ if (maxfd < sock_cli) maxfd = sock_cli; /*设置超时时间*/ tv.tv_sec = 10; tv.tv_usec = 0; /*等待聊天*/ retval = select(maxfd+1, &rfds, NULL, NULL, &tv); if (retval == -1){ printf ( "select出错,客户端程序退出\n" ); break ; } else if (retval == 0){ printf ( "客户端没有任何输入信息,并且服务器也没有信息到来,waiting...\n" ); continue ; } else { /*服务器发来了消息*/ if (FD_ISSET(sock_cli,&rfds)){ char recvbuf[BUFFER_SIZE]; int len; len = recv(sock_cli, recvbuf, sizeof (recvbuf),0); printf ( "%s" , recvbuf); memset (recvbuf, 0, sizeof (recvbuf)); } /*用户输入信息了,开始处理信息并发送*/ if (FD_ISSET(0, &rfds)){ char sendbuf[BUFFER_SIZE]; fgets (sendbuf, sizeof (sendbuf), stdin); send(sock_cli, sendbuf, strlen (sendbuf),0); //发送 memset (sendbuf, 0, sizeof (sendbuf)); } } } close(sock_cli); return 0; } |
-------------------------------------------------------------------------------------------------------------
局域网通过UDP实现服务端和客户端的通信, UDP的服务端不需要执行listen函数和accept函数:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架