网络编程-套接字(scoket)

来源:互联网 发布:杨辉三角c语言程序7行 编辑:程序博客网 时间:2024/04/29 02:51

socket编程 

socket这个词可以表示很多概念: 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”就称为socket。  

在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。socket本身有“插座”的意思,因此用来描述网络连接的一 对一关 系。  TCP/IP协议最早在BSD UNIX上实现,为TCP/IP协议设计的应用层编程接口称为socket API。


在网络中,必须保证数据是大端存储。必须从低地址处发送,数据是高位的。
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6、UNIX Domain Socket。然而,各种网络协议的地址格式并不相同,如下图所⽰示:  sockaddr数据结构  


IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,IPv6地址用sockaddr_in6结构体表示,包括16位端口号、128位IP地址和一些控制字段。UNIX Domain Socket的地址格式定义在sys/un.h中,用sockaddr_un结构体表示。各种socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并不是所有 UNIX的实现 都有长度字段,如Linux就没有),后16位表示地址类型。IPv4、IPv6和UNIX Domain Socket的地址类型分别定义为常数AF_INET、AF_INET6、AF_UNIX。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。因此,socket API可以接受各种类型的sockaddr结构体指针做参数,例如bind、accept、connect等函数,这些函数的参数应该设计成void *类型以便接受各种类型的指针,但是sock API的实现早于ANSI C标准化,那时还没有void *类型,因此这些函数的参数都用struct sockaddr *类型表示,在传递参数之前要强制类型转换一下。


基于TCP协议的网络协议的一般流程:

创建套接字(scoket)
int scoket(AF_INET,SOCK_STREAM,0)
     返回值: 失败返回-1;
                    成功返回文件描述符;
绑定套接字
int bind(listen_sock,(struct sockaddr*)&local,sizeof(local));
返回值:
              失败返回-1;
设置套接字的状态(监听状态)
 int listen(int scokfd,5);
返回值:    
                失败返回-1;
 
accept链接
成功就返回一个新建的scoket的描述符;

服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状 态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从 accept()返回。  
数据传输的过程: 建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从 accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送 请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。  
如果客户端没有更多的请求了,就调用close() 关闭连接,就像写端关闭的管道一样,服务器的 read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close() 后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown() 则连接处于半关闭状态,仍可接收对方发来的数据。 

基于TCP协议的单进程客户端/服务器的程序:




运行结果:



基于TCP协议的多进程客户端/服务器的程序:





运行结果:


基于TCP协议的多线程客户端/服务器的程序:




运行结果:


下面来看一个错误:


这是为什么呢?

虽然server的应用程序终止了,但TCP协议层的连接并没有 完全断开,因此不能再次监听同样的server端口。

server的TCP连接收到client发的FIN段后处于TIME_WAIT状 态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime) 的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所 以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端 口。MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同,在Linux上一般经过半分钟后 就可以再次启动server 了。


如何解决:

使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1, 表⽰允许创建端口号相同但IP地址不同的多个socket描述符。 


int opt = 1;

setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));


1 0