好多天前就想写的,可是拖延症暂时没药医。。。
socket编程过程中,有几个前提(在UNP的前言以及第一章中有提到)。1.需要一定的编程语言基础,例如C语言;2.需要有一定的TCP/IP网络协议栈知识,至少知道TCP和UDP的连接和释放(话说UDP不需要连接);3.如果可能,最好对操作系统(例如Linux)有一定了解。然后,就可以开搞了。
一般而言,socket编程分为客户端和服务端编程,socket提供在这两者之间进行信息交互。基本的流程如下:
其中,read和write属于Linux/Unix下的系统调用,通常还可以用recv和send函数代替。这张图引用自UNP第四章,使用系统调用的原因主要是为了表明,socket函数返回的socket描述符可以和Linux系统的本地文件描述符同样对待,因此也能够使用close函数来关闭一个套接字。
TCP属于全双工通信,因此一个套接字描述符拥有两个缓冲区,一个发送,一个接收,即tcp socket可以同时进行数据的收发而相互不影响。
对于客户端而言,socket函数产生套接字描述符,connect函数连接到服务端,服务端信息(例如ip地址,端口号等)放在一个socket_in结构中。通常而言,客户端不需要使用bind函数,但如果非要使用,也可以把客户端绑定到一个特定的ip或端口号(这两个可以任意组合)。其中,端口号小于1024(系统服务使用)需要root权限分配,ip地址则必须是本地网络接口的ip地址,即bind函数可以将客户端绑定到本机的一个特定的网络接口上,这样往外发数据就都使用这个特定的接口,否则根据目的地址的不同会通过不同的接口发送。例如,如果不绑定ip地址,发送数据到本地环回地址(127.0.0.1)将同样使用本地接口,而到本机的局域网地址则使用有线网络接口(例如eth0),具体连接可以通过netstat查看。因此,如果要限制系统中只能开一个客户端,可以将客户端的ip和端口全部绑死,这样开第二个客户端的时候connect函数就会返回“Address already in use”错误。
关于文件描述符(套接字描述符),在看UNP谈及最简单的并发时(使用fork),循环里是这样写的:
for(;;){ connfd=accept(listen_fd,NULL,NULL); if((pid=fork())==0){ close(listen_fd); do_something(); close(connfd); exit(0); } close(connfd); }
这里涉及到fork函数的调用。 fork函数创建了一个新的进程,然后把旧进程的一切都复制了一遍(实际上只有两者产生不同时才复制),所以实际上就存在了两个监听套接字和连接套接字描述符,在Linux中就是对套接字描述符的链接计数器+1,所以在子进程中关闭监听套接字只是计数器-1的操作,不会真的关闭连接。
初学socket编程时,容易遇到许多的坑,例如,TCP的状态转换,见下图:
TCP连接的状态并不是《计算机网络》(谢希仁版,教材)那本书中提到那么简单,而且连接终止也更加细化了一些。如果不能理解(记住)这些状态,时不时就要被坑一下。
又例如,socket使用网络进行数据传输,所以各种网络中的突发状况都会在通信过程中发生,而服务端和客户端也可能发生许多不可预知的错误。例如,服务器或客户端异常关闭,数据的字节序不同,客户端和服务端字大小不同(例如一个是32位另一个是64位),网络突然断开等等。
所以,简单的处理就是对各个函数调用过程中产生的错误信息(产生信号)进行相应的处理,例如添加信号处理函数等。