当前位置: 首页 > Linux

linuxsocket编程-socket接口

时间:2023-04-07 03:04:56 Linux

常用socket函数Windows和Linux上常用的socketAPI函数并不多,除了特定操作系统提供的一些基于自身系统特性的API外,大部分的SocketAPI都来源于BSDSocket(即BerkeleySockets),因此这些套接字函数在不同平台上具有相似的签名和参数。想学习网络编程的新手经常会问要掌握哪些基本的socketAPI。这里我给出一个简单的函数列表,里面都是应该掌握的socket函数。常用BerkeleySocketsAPI列表函数名称函数简要说明附加说明socket创建某种类型的socketbind将socket绑定到ip的二元组andportlisten将socket转为监听状态connect尝试建立TCP连接一般用于clientaccept尝试接收连接一般用于serversend通过socket发送数据recv通过socket接收数据selectto判断一组socket上的read事件gethostbyname通过域名获取机器地址close关闭一个socket,回收socket对应的资源。Windows系统对应closesocketshutdown关闭socket接收或发送通道。setsockopt设置套接字选项getsockopt获取套接字选项。socketsocket()函数的原型如下,协议类型为type,协议号为protocol的socket文件描述符。如果函数调用成功,它将返回一个标识套接字的文件描述符,失败则返回-1。#include/*参见注释*/#includeintsocket(intdomain,inttype,intprotocol);域函数socket()的参数domain用于设置网络通信域,函数socket()根据该参数选择通信协议族。通信协议族在文件sys/socket.h中定义。名称含义名称含义PF_UNIXPF_LOCAL本地通信PF_X25ITU-TX25/ISO-8208协议AF_INET,PF_INETIPv4Internet协议PF_AX25AmateurradioAX.25PF_INET6IPv6Internet协议PF_ATMPVCRawATMPVCAccessPF_IPXIPX-NovellProtocolUserPACKtypePF_InterfaceDeviceAccessKernel()的参数类型用于设置socket通信的类型,主要有SOCKET_STREAM(流式socket)、SOCK——DGRAM(数据包socket)等。名称含义SOCK_STREAMtcp连接,提供序列化、可靠、双向的连接字节流。支持带外数据传输SOCK_DGRAM支持UDP连接(无连接状态的消息)SOCK_SEQPACKET序列化包,提供一个序列化的、可靠的、数据长度恒定的双向基本连接数据传输通道。每次调用read系统调用,都需要读出SOCK_RAWRAW类型的所有数据,提供原始网络协议访问SOCK_RDM提供可靠的数据包,但数据可能乱序,并非所有协议系列实现这些协议类型。例如,AF_INET协议族没有实现SOCK_SEQPACKET协议类型。协议函数socket()的第三个参数protocol,用于制定一个协议的具体类型,即type类型中的某个类型。通常某个协议中只有一个特定的类型,所以协议参数只能设置为0;但有些协议有多种特定类型,需要设置该参数来选择特定类型。errno函数socket()并不总是执行成功,可能会出现错误。错误的原因有很多,可以通过errno获取:value含义EACCES没有权限创建指定的域类型socketEAFNOSUPPORT不支持给定的地址类型EINVAL不支持这个协议或者协议不可用EMFILE进程文件表溢出ENFILE已达到系统允许的打开文件数,打开文件过多ENOBUFS/ENOMEM内存不足。套接字只有足够的资源或进程释放内存。EPROTONOSUPPORT制定的协议类型在域中不存在。例如,我们可以像这样创建一个流套接字:intsock=socket(AF_INET,SOCK_STREAM,0);bind在socket中,socket只是用户程序和内核之间交换信息的一个枢纽。它本身没有太多信息,也没有网络协议地址、端口号等信息。在进行网络通信时,套接字必须与地址相关联。这个过程就是地址绑定的过程。很多时候内核会自动给我们绑定一个地址,但有时可能需要用户自己完成绑定过程,以满足实际应用的需要。最典型的情况是一个服务器进程需要绑定一个众所周知的地址或端口来等待客户端连接。这是由绑定函数完成的。intbind(intsockfd,structsockaddr*addr,socklen_taddrlen)sockfd是我们调用socket函数后创建的socket句柄或文件描述符。addraddr是一个指向sockaddr参数的指针,它包含地址、端口和IP地址信息。在进行地址绑定时,需要在绑定前的address结构中的structsockaddr结构体中设置IP地址、端口、类型等字段,使socket文件描述符和地址等连接在一起。由于历史原因,我们有两种地址结构:structsockaddr结构定义如下:structsockaddr{uint8_tsa_len;  unsignedshortsa_family;/*地址族,AF_xxx*/  charsa_data[14];/*14字节协议地址*/  };其实这个结构逐渐被废弃了,但是因为历史的原因,很多函数,比如connect,bind等,还是用这个作为声明,其实现在用的是第一个,这两个结构,我们需要将第二个结构转换为sockaddr。structsockaddr_in定义如下:structsockaddr_in{  uint8_tsa_len;/*结构长度*/shortintsin_family;/*通信类型*/  unsignedshortintsin_port;/*端口*/  structin_addrsin_addr;/*互联网地址*/  unsignedcharsin_zero[8];/*未使用*/  };structin_addr{//sin_addr结构类型in_addr原型  unsignedlongs_addr;/*存储4字节的IP地址(以网络字节顺序)。*/  };在使用的时候,我们必须指定通信类型,还必须将端口号和地址转换成网络顺序的字节序addrlenaddr结构体的长度可以设置为sizeof(structsockaddr)。使用sizeof(structsockaddr)设置套接字的类型及其对应的结构。当bind()函数的返回值为0时表示绑定成功,-1表示绑定失败,errno的错误值如表1所示。值含义备注EADDRINUSE给定地址已使用EBADFsockfd无效EINVALsockfd已绑定到另一个地址ENOTSOCKsockfd是文件描述符,不是套接字描述符不是本地UNIX协议族,AF_UNIXEFAULTmy_addr指针超出用户空间UNIX协议族,AF_UNIXEINVAL地址长度错误,或者套接字不是AF_UNIX族UNIX协议族,AF_UNIXELOOP解析my_addr太多符号链接UNIX协议族,AF_UNIXENAMETOOLONGmy_addr太长UNIX协议族,AF_UNIXENOENT文件不存在UNIX协议族,AF_UNIXENOMEN内存内核不足memset(&addr,0,sizeof(structsockaddr_in));地址.sin_family=AF_INET;地址.sin_port=htons(端口);addr.sin_addr.s_addr=INADDR_ANY;if(bind(sfd,(structsockaddr*)&addr,sizeof(structsockaddr_in))<0){perror("bind");退出(1);}listenintlisten(intsockfd,intbacklog);listen()函数将sockfd标记为一个被动打开的套接字,并将其作为accept的参数来接收传入的连接请求。sockfd是socket类型的文件描述符,具体类型为SOCK_STREAM或SOCK_SEQPACKET。backlog参数用来描述sockfd的等待连接队列可以达到的最大值。当有请求到达,队列已满时,客户端可能会收到连接失败的错误,或者如果底层协议支持重传(如tcp协议),则该请求会被丢弃不处理,下次重试预计连接会成功(队列可能已经为下一次重传腾出了空间)。说起这个积压,还有一点历史,下面会讲到。errno值表示EADDRINUSE另一个套接字已经绑定在同一个端口上。EBADF参数sockfd不是有效的文件描述符。ENOTSOCK参数sockfd不是套接字。EOPNOTSUPP参数sockfd不是支持监听操作的套接字类型。连接语句如下:intconnect(intsockfd,conststructsockaddr*addr,socklen_taddrlen);参数说明如下:sockfd是系统调用socket()返回的socket文件描述符。serv_addr是一个数据结构structsockaddr_in,它保存了目标端口和IP地址。addrlen设置为sizeof(structsockaddr_in)errnoconnect函数在调用失败时返回值-1,并设置全局错误变量errno。值含义EBADF参数sockfd非法套接字处理代码EFAULT参数serv_addr指针指向不可访问的内存空间ENOTSOCK参数sockfd是文件描述符,不是套接字。EISCONN参数sockfd的socket已经处于连接状态ECONNREFUSED连接请求被服务器拒绝。ETIMEDOUT连接尝试在时限内没有响应。ENETUNREACH无法将数据包传送到指定的主机。EAFNOSUPPORTsockaddr结构的sa_family不正确。接受函数声明intaccept(intsockfd,structsockaddr*addr,socklen_t*addrlen);参数说明sockfd是socket函数返回的套接字描述符,参数addr和addrlen用于返回连接的对等进程(client)协议地址。如果我们对客户端的协议地址不感兴趣,我们可以将arrd和addrlen都设置为空指针。返回值成功时,返回一个非负整数,即接收到的socket的描述符;当发生错误时,返回-1,并相应地设置全局变量errno。值含义EBADFinvalidsocketEFAULTparameteraddrpointerpointstothememoryspacethatcan'taccessed接下来就是这个accept是一个阻塞函数。对于阻塞套接字,它会一直阻塞,或者对于非阻塞套接字返回一个错误值。accept可能返回-1,但如果errno的值为EAGAIN或EWOULDBLOCK,则需要再次调用accept函数。send_recv发送和接收函数声明如下ssize_trecv(intsockfd,void*buf,size_tlen,intflags);ssize_t发送(intsockfd,constvoid*buf,size_tlen,intflags);sockfd:socketbuf:Bufferlentobesentorreceived:如果是recv,指的是期望接收的长度,如果是send,指的是要发送的长度。flags:标志位,取值如下:flags说明recvsendMSG_DONTROUTE绕过路由表查找MSG_DONTWAIT非阻塞只对本次操作MSG_OOB    发送或接收带外数据等待所有数据?errno值含义EAGAIN套接字已被标记为非阻塞,并且接收操作被阻塞或接收超时EBADFsock不是有效的描述符ECONNREFUSE远程主机阻塞网络连接EFAULT内存空间访问错误EINTR操作被信号中断并且面向连接的sockets还没有连接上ENOTSOCKsockindexisnotasocket当返回值为0时,连接正常关闭;当返回值为-1时,一定是错误,当返回值为0时怎么办?如何正确判断一个peer关闭了连接?/*客户端设置非阻塞,然后判断是否连接成功*/intSocketConnectWithTimeout(intmySocket,structmySocketaddr*adrs,intadrsLen,structtimeval*timeVal){intflag;fd_setwriteFds;intremotPeerAdressLen;结构mySocketaddrremotPeerAdress;如果(timeVal==NULL){返回(连接(mymySocket,adrs,adrsLen));}flag=fcntl(mySocket,F_GETFL,0);fcntl(mySocket,F_SETFL,flag|O_NONBLOCK);//修改当前flag为Blocking//对于非阻塞sockets,如果调用connect函数,则返回-1(表示错误),错误为EINPROGRESS,表示连接建立if(connect(mySocket,adrs,adrsLen)<0){//使用非阻塞模式时,如果没有立即建立连接,则connect()returnsEINPROGRESSif(errno==EINPROGRESS){//select是一种IO多路复用机制,允许进程指示内核等待多个事件中的任何一个发生,以及一个或多个事件发生时或之后aspecifiedperiodoftimewakeitlater//connect本身没有设置超时的功能,如果想给socket的IO操作设置超时,可以使用select函数。这时候我们通过不断检测writeFds来判断链接的建立?FD_ZERO(&writeFds);FD_SET((unsignedint)mySocket,&writeFds);if(select(FD_SETSIZE,(fd_set*)NULL,&writeFds,(fd_set*)NULL,timeVal)>0){//select()成功,检查mySocketet是否可写(key)if(FD_ISSET((unsignedint)mySocket,&writeFds)){//已经是可写的了,这时候我们需要用getpeername()判断是否真正连接成功,如果返回值不是-1;//表示connect()成功。remotPeerAdressLen=sizeof(remotPeerAdress);if(getpeername(mySocket,&remotPeerAdress,&remotPeerAdressLen)!=ERROR){返回OK;}else{返回错误;}}}}else{返回错误;}}SETSfcntag,Fcntl(;//恢复标志为阻塞}1.将打开的socket设置为非阻塞,可以用fcntl(socket,F_SETFL,O_NDELAY)来完成(有些系统也可以用FNEDLAY)。2.发送connect调用,此时返回-1,但errno设置为EINPROGRESS,表示connect仍未完成3.将opensocket设置为监听可写(注意不可读)文件集合并用select监控,如果可写,则使用getsockopt(socket,SOL_SOCKET,SO_ERROR,&error,sizeof(int));获取error的值,如果为0则连接成功。