这篇文章略作删减后转过来了。主要有以下几点值得自己注意的:
(1)刚开头对套接字的理解。
(2)缓冲区的理解。
其他部分有时间重新整理。
套接字的概念及分类
在网络中,要全局的标识一个参与通信的进程,需要三元组:协议,IP地址以及端口号。要描述两个应用进程之间的端到端的通信关联需要五元组:协议,信源主机IP,信源应用进程端口,信宿主机IP,信宿应用进程端口。为了实现两个应用进程的通信连接,提出了套接字的概念。套接字可以理解为通信连接的一端,将两个套接字连接在一起,可以实现不同进程之间的通信。
针对不同的通信需求,TCP/IP中提供了三种不同的套接字:
(1)流套接字(SOCK_STREAM)
流套接字用于面向连接,可靠的数据传输服务。它之所以能实现可靠的数据服务,是因为它使用了传输控制协议TCP。适合传输大量的数据,但是不支持广播和组播。
(2)数据报套接字(SOCK_DGRAM)
数据报套接字提供了一种无连接的服务,通信双方不需要建立任何显示连接,数据可以发送到指定的套接字。数据报套接字使用UDP进行数据传输,支持广播和组播方式。
(3)原始套接字(SOCK_RAW)
原始套接字与标准套接字(上面两个)区别在于:原始套接字可以读写内核没有处理的IP数据报;流套接字只能读写TCP数据报,数据报套接字只能读写UDP数据报。原始套接字的主要目的是避开TCP/IP的处理机制,被传送的数据报可以直接传送给需要他的程序。主要用于编写自定义地层协议的应用程序。
socket相关函数
1.socket函数:
功能:为应用程序创建套接字。
格式:SOCKET socket(int af, int type, int protocol)。
参数:af-套接字使用的协议地址族,如果使用TCP或者UDP,只能使用AF_INET;type-套接字协议类型,如SOCK_STREAM、SOCK_DGRAM;protocol-套接字使用的特定协议,如果不希望特别指定协议类型,则设置为0。
返回值:函数成功调用后返回一个新的套接字,是一个无符号的整型数据;失败时返回INVALID_SOCKET。
说明:应用程序在使用套接字通信之前,必须拥有一个套接字。
2.bind函数:
功能:实现套接字与主机本地IP地址和端口号的绑定。
格式:int bind(SOCKET s, const struct sockaddr *name, int namelen)。
参数:s-将要绑定的套接字;name-与指定协议有关的地址结构指针;namelen-name参数的长度。
返回值:函数成功时返回0;失败时返回SOCKET_ERROR。
3.listen函数:
功能:设定套接字为监听状态,准备接收由客户机进程发出的连接请求。
格式:int listen(SOCKET s, int backlog)。
参数:s-已绑定地址,但还未建立连接的套接字标识符;backlog-指定正在等待连接的最大队列长度。
返回值:函数成功时返回0;失败时返回SOCKET_ERROR。
说明:仅适用于面向连接的套接字,且用于服务器进程。
4.connect函数:
功能:提出与服务器建立连接的请求,如果服务器进程接受请求,则服务器进程与客户端进程之间便建立了一条通信连接。
格式:int connect(SOCKET s, const struct sockaddr FAR *name, int namelen )。
参数:s-欲要建立连接的套接字;name-指向通信对方的套接字地址结构指针,表示s欲与其建立连接;namelen-name参数的长度。
返回值:函数成功时返回0;失败时返回SOCKET_ERROR。
说明:在客户端进程调用该方法请求建立连接时,将激活建立连接的3次握手,以此来建立一条与服务器进程的TCP连接。如果该函数调用之前没有绑定地址,系统自动绑定本地地址到此套接字。
5.accept函数:
功能:接受客户端进程调用connect函数发出的连接请求。
格式:SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen)。
参数:s-处于侦听状态的套接字(监听socket);addr-指向一个用来存放发出连接请求的客户机进程IP地址信息的地址结构指针;addrlen-addr的长度。
返回值:调用成功返回一个新的套接字,这个套接字对应于已接受的那个客户机进程的连接,失败时返回INVALID_SOCKET。
说明:用于面向连接的服务器进程,在IP协议族中只适用于TCP服务器端。
7.shutdown函数:
功能:关闭套接字读写通道,即停止套接字接受传递的功能。
格式:int shutdown(SOCKET s, int how)。
参数:s-套接字;how-描述停止哪些操作。
how-0:不再接收消息;
how-1:不再允许发送消息;
how-2:既不接收消息,也不再发送消息。
返回值:函数成功时返回0;失败时返回SOCKET_ERROR。
说明:只是停止套接字的功能,并没有关闭套接字,套接字的资源还没释放。
8.close函数:
功能:关闭套接字,释放与套接字关联的所有资源。
格式:int close(int fd)。
参数:s-将要关闭的套接字。
返回值:函数成功时返回0;失败时返回SOCKET_ERROR。
说明:当套接字s的数据缓冲队列中还有未发出的数据时,如果套接字设定为SO_DONTLINGER,则等待数据缓冲队列中的数据继续传输完毕关闭该套接字;如果套接字设定为SO_LINGER,则分以下两种情况:
(1)Timeout设为0,套接字马上关闭,数据缓冲队列中数据丢失。
(2)Timeout不为0,等待数据传输完毕或者Timeout为0时关闭套接字。
9.recv函数:
功能:在已建立连接的套接字上接收数据。
格式:int recv(SOCKET s, char *buf, int len, int flags)。
参数:s-已建立连接的套接字;buf-存放接收到的数据的缓冲区指针;len-buf的长度;flags-调用方式:
(1)0:接收的是正常数据,无特殊行为。
(2)MSG_PEEK:系统缓冲区数据复制到提供的接收缓冲区,但是系统缓冲区内容并没有删除。
(3)MSG_OOB:表示处理带外数据。
返回值:接收成功时返回接收到的数据长度,连接结束时返回0,连接失败时返回SOCKET_ERROR。
10.send函数:
功能:在已建立连接的套接字上发送数据.
格式:int send(SOCKET s, const char *buf, int len, int flags)。
参数:参数:s-已建立连接的套接字;buf-存放将要发送的数据的缓冲区指针;len-发送缓冲区中的字符数;flags-控制数据传输方式:
(1)0:接收的是正常数据,无特殊行为。
(2)MSG_DONTROUTE:表示目标主机就在本地网络中,无需路由选择。
(3)MSG_OOB:表示处理带外数据。
返回值:发送成功时返回发送的数据长度,连接结束时返回0,连接失败时返回SOCKET_ERROR。
套接字编程流程
socket缓冲区
每一个socket在被创建之后,系统都会给它分配两个缓冲区,即输入缓冲区和输出缓冲区。
(1)send函数并不是直接将数据传输到网络中,而是负责将数据写入输出缓冲区,数据从输出缓冲区发送到目标主机是由TCP协议完成的。数据写入到输出缓冲区之后,send函数就可以返回了,数据是否发送出去,是否发送成功,何时到达目标主机,都不由它负责了,而是由协议负责。
(2)recv函数也是一样的,它并不是直接从网络中获取数据,而是从输入缓冲区中读取数据。
输入输出缓冲区,系统会为每个socket都单独分配,并且是在socket创建的时候自动生成的。一般来说,默认的输入输出缓冲区大小为8K(关于这块参见这里)。套接字关闭的时候,输出缓冲区的数据不会丢失,会由协议发送到另一方;而输入缓冲区的数据则会丢失。
socket数据发送与接收问题
数据的发送和接收是独立的,并不是发送方执行一次send,接收方就执行一次recv。recv函数不管发送几次,都会从输入缓冲区尽可能多的获取数据。如果发送方发送了多次信息,接收方没来得及进行recv,则数据堆积在输入缓冲区中,取数据的时候会都取出来。换句话说,recv并不能判断数据包的结束位置。
send函数:
在数据进行发送的时候,需要先检查输出缓冲区的可用空间大小,如果可用空间大小小于要发送的数据长度,则send会被阻塞,直到缓冲区中的数据被发送到目标主机,有了足够的空间之后,send函数才会将数据写入输出缓冲区。
TCP协议正在将数据发送到网络上的时候,输出缓冲区会被锁定(生产者消费者问题),不允许写入,send函数会被阻塞,直到数据发送完,输出缓冲区解锁,此时send才能将数据写入到输出缓冲区。
要写入的数据大于输出缓冲区的最大长度的时候,要分多次写入,直到所有数据都被写到缓冲区之后,send函数才会返回。
recv函数:
函数先检查输入缓冲区,如果输入缓冲区中有数据,读取出缓冲区中的数据,否则的话,recv函数会被阻塞,等待网络上传来数据。如果读取的数据长度小于输出缓冲区中的数据长度,没法一次性将所有数据读出来,需要多次执行recv函数,才能将数据读取完毕。
循环发送和接收
防止send或者 recv不完整,这样你想发一个
几MB直接调用下面方法就okay,不会少发~
bool SendAll(SOCKET &sock, char*buffer, int size){while (size>0){int SendSize= send(sock, buffer, size, 0);if(SOCKET_ERROR==SendSize)return false;size = size - SendSize;//用于循环发送且退出功能buffer+=SendSize;//用于计算已发buffer的偏移量}return true;}bool RecvAll(SOCKET &sock, char*buffer, int size){while (size>0)//剩余部分大于0{int RecvSize= recv(sock, buffer, size, 0);if(SOCKET_ERROR==RecvSize)return false;size = size - RecvSize;buffer+=RecvSize;}return true;}