[toc]
本人博客https://qinzheng7575.github.io/
前言
在之前写的socket通信程序中,有一个服务器,一接收到消息就会回复一个ACK;有一个客户机,一直等待输入按下回车就可发送。这似乎已经很完美了,能够实现通信,但是,稍微想想它和我们日常使用的聊天程序QQ、微信之间的区别,就能够发现,哦,原来完全没法做到真正的“自由聊天”。
目标与关键技术
- 编写两对简单的一对一聊天程序,分别为面向连接方式和无连接方式
- 双方可以自由聊天,即随时都可以输入或显示对方的数据
要实现第二个,看起来很简单,其实需要引入全新的机制:多路复用机制
我们能不能不用非得等到对面回复了东西才能继续发?能不能在scanf
阻塞等待输入的时候,我先接收对面的东西?要想避免线性流水线式的程序模型,就必须引入多路复用,而select
机制,就是时分复用实现多路复用的形式。
select()
功能:检查多个套接字状态,将其放在对应的队列中,我们就可以根据不同的队列来进行操作了
意味着:每次只要是队列里的socket,就必定代表它发生了某种事情,导致了它在这个队列里面,那么我们接下来的操作肯定可以的
Linux一切皆文件
在Linux中,可以看到明显的编程风格区别,socket套接字不再是由SOCKET定义,而是int,其实就是因为,在操作系统看来一切都是文件,socket也不过是一个可以读、写等操作的文件描述符罢了。
同时,这也意味着,我们就可以直接把文件描述符0,1,2(标准输入、输出、错误),放到select()
中,让输入文字也能够和接收socket消息并行了!
套接字队列
我们在select()
中,需要填三个队列,分别为read_fds
、write_fds
、except_fds
。对套接字队列的操作,有初始化,插入,删除,查找,遍历等。而select()
实际生就是帮我们把各个socket文件描述符,放到其该存在的位置,比如处于accpet
后的套接字,一旦对方send
了东西,那么它就变得可读了,经过select()
后也就被放在了read_fds
队列中。
当然,我们还需要手动创建一个套接字管理队列:因为套接字可能被从套接字队列中删除,需要一个班级的花名册来记录,方便下次再放进套接字队列中。
非阻塞
select我们要开启非阻塞模式,否则它就会阻塞掉,使得没法输入文字了,并且需要设置超时时间,避免忙等消耗掉计算机资源。
代码解析
完整代码请看本人的Github https://github.com/Qinzheng7575
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
| #define STDIN 0 struct socket_list { int MainSock; int num; int socket_array[256]; }; int main(){ int s, sock; struct sockaddr_in ser_addr, remote; unsigned int len; char buf[128]; struct socket_list sock_list; fd_set readfds, writefds, exceptfds; int i; unsigned long arg; struct timeval timeout; int retval; s = socket(AF_INET, SOCK_DGRAM, 0); ser_addr.sin_family = AF_INET; ser_addr.sin_addr.s_addr = htonl(INADDR_ANY); ser_addr.sin_port = htons(0x1234); bind(s, (struct sockaddr*)&ser_addr, sizeof(ser_addr)); remote.sin_family = AF_INET; remote.sin_addr.s_addr = htonl(INADDR_LOOPBACK); remote.sin_port = htons(0x4321); timeout.tv_sec = 2; timeout.tv_usec = 0; init_list(&sock_list); FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); arg = 1; int fd = STDIN; ioctl(s, FIONBIO, &arg); ioctl(fd, FIONBIO, &arg); while (1) { FD_ZERO(&readfds); insert_list(s, &sock_list); make_fdlist(&sock_list, &readfds); make_fdlist(&sock_list, &writefds); make_fdlist(&sock_list, &exceptfds); FD_SET(fd, &readfds); retval = select(1024, &readfds, &writefds, &exceptfds, &timeout);
for (i = 0; i < 64; i++) { if (sock_list.socket_array[i] == 0)continue; sock = sock_list.socket_array[i]; if (FD_ISSET(STDIN, &readfds)) { char in_buf[128]; read(STDIN, in_buf, 127); len = sizeof(remote); int a; if (strlen(in_buf) > 0) { printf("have read from keyboard:%s\n", in_buf); a = sendto(s, in_buf, strlen(in_buf), 0, (sockaddr*)&remote, len); memset(in_buf, 0x00, sizeof(char) * 128); printf("send ok %d\n", a); } }
if (FD_ISSET(sock, &readfds)) { len = sizeof(remote); int postion = 0; postion = recvfrom(s, buf, 127, 0, (struct sockaddr*)&remote, &len); buf[postion] = '\0'; if (strlen(buf) > 0) { printf("接收到:%s\n", buf); memset(buf, 0x00, sizeof(char) * 128); } } if (FD_ISSET(sock, &writefds)) {} if (FD_ISSET(sock, &exceptfds)) {} } } FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); close(s); return 0; }
|
最后实现的,就是两个terminal能够自由的相对方发送数据,进行聊天!
通过测试界面我们可以看到,在一段打字的时候,并不会影响其接受数据,同时也能够任意发连续的文字,实现自由通信。
Copyright Notice: 此文章版权归秦政所有,如有转载,请注明來自原作者