18910140161

UNIX套接字编程

顺晟科技

2021-06-16 10:38:04

250

再回头看一遍socket的知识点,简单总结一下。如有问题,请随时指出。顺便做个广告-游戏开发讨论组:462686014

在UNIX中,创建套接字时,就像打开文件一样,从描述符表中检索int类型的索引号。套接字和文件是共享的描述符表,因此它们的索引号不能重复。进程可以同时创建的套接字和文件的更大数量是相同的。

下面描述开发中使用的函数。

1.套接字的创建和关闭

创建socket时使用的函数是socket(),其原型如下(注意:函数参数的详细介绍不再赘述)

int socket(int af,int type,int protocol);

套接字函数的返回值是新创建的套接字的int索引。如果套接字创建失败,socket函数返回值为-1,具体错误原因可以通过errno函数找到。

下面是socket()函数的一个例子。

/*

套接字的创建函数在成功创建时返回套接字索引,在创建失败时返回-1。

*/

int CreateSocket()

{

int newSocket

newSocket=socket(AF_INET,SOCK_STREAM,0);

if(新闻时钟0)

return-1;

返回newSocket

}

当我们想要终止套接字连接或使用套接字时,我们需要返回套接字资源。与文件一样,使用封闭套接字返回是通过函数close()完成的。close()的原型如下:

int close(int d);

close的参数是要关闭的套接字的索引。这里有一个问题需要考虑。如果还有数据要传输到另一台主机或者另一台主机发送的数据还没有处理,默认的处理方法是在整理完close函数等未处理的数据后返回套接字的资源。

除了关闭功能,关机还可以关闭插座。一般来说,关闭功能是最常用的,但是关闭功能提供了更多的选项。关机功能的原型如下:

int shutdown(int socket,int direction);

2.阻塞和非阻塞

在套接字编程中,有两种模式:“阻塞模式套接字”和“非阻塞模式套接字”。这两种套接字模式的区别在于,当创建的套接字用于处理时,比如调用特定的套接字函数发送或接收数据,仍然是不改变函数的结果。

当其他主机传输的数据存储在阻塞创建的套接字中时。我们以程序变量使用的函数recv的数据为例。如果有10个字节的数据可用,当使用recv函数时,recv函数将在提取10个字节后返回。但是,如果recv函数没有可用的数据,recv函数会等待可用的数据,并且不会返回这些数据。

相反,如果一个套接字是以非阻塞的方式创建的,那么在调用recv函数的时候,如果有想要的数据,就会被取入程序的缓冲区,如果没有想要的数据,就会返回错误消息。实际上,套接字创建的套接字处于阻塞模式,如果您想转换到非阻塞模式,您需要执行以下操作:

/*

将套接字设置为非阻塞模式

*/

无效非块(int sock)

{

int flags=fcntl(sock,F _ GETFL,0);

标志|=O _ NONBLOCK

if (fcntl(sock,F_SETFL,flags) 0)

出口(1);

}

fcntl函数是一个UNIX函数,它提供了获取或修改文件描述符(如套接字)属性的功能。

3.与其他主机连接

在OSI协议层的分组交换网络的网络协议中,连接是通过“两次握手”来建立的。这样,接受连接请求的主机发送一个连接批准响应,就可以实现连接,即关于建立连接的消息,双方只能发送一次。

在TCP/IP中,建立连接的方式不是“两次握手”,而是“三次握手”。在“双向握手”模式下,建立连接的双方只需要交换两次数据就可以建立连接,而在“三次握手”模式下,建立连接需要交换三次数据。

与其他主机建立连接的功能是connect(),connect功能的原型如下:

int connect(int s,const struct sockaddr *name,int namelen);

成功调用connect函数时,即与其他主机连接成功时,函数返回值为0;否则,它返回-1。如果需要检查详细的错误原因,可以使用errno函数进行检查。

下面是connect函数的一个应用示例。

/*

使用指定的参数int sock与本地主机(127.0.0.1)的端口8081建立连接

成功返回1,未能返回0

*/

int ConnectToServer(int sock)

{

struct sockaddr _ in addr _ in

addr _ in.sin _ family=AF _ INET

addr _ in . sin _ addr . s _ addr=inet _ addr(' 127 . 0 . 0 . 1 ');

addr _ in . sin _ port=htons(8081);

if (connect(sock,(struct sockaddr*)addr_in,sizeof(addr_in) 0)

返回0;

返回1;

}

除了使用ip与主机建立连接外,还可以使用DNS进行连接。使用域名时,不能使用inet_addr函数将ip地址转换为二进制形式来获取对方主机的目的地址。要将域名转换为二进制形式的地址,您需要使用如下所示的gethostbyname函数。

/*

传递给char* adrr参数的互联网域名地址被转换为长地址形式

*/

无符号长GetAddrebyDomian(char * addr)

{

struct hostent * ph

struct in _ addrin

ph=get hostname(addr);

如果(ph==空)

返回空值;

memcpy((char**)(in),ph-h_addr,ph-h _ length);

return in.s _ addr

}

在上面的函数中,使用gethostname函数将域名转换成hostent结构并返回,在hostent结构中获得二进制形式的地址。一般引用hostent结构定义的变量,使用成员h_addr,指向h_addr_list的个指针变量的地址。下面是一个使用GetAddrBydomian函数连接到服务器的应用示例。

/*

您可以使用ip地址或域名的连接功能

成功返回1,未能返回0

*/

int ConnectToServer(int sock,char* address,int port,int isDomain)

{

struct sockaddr _ in addr _ in

addr _ in.sin _ family=AF _ INET

addr_in.sin_port=htons(端口);

if (isDomain)

addr _ in . sin _ addr . s _ addr=GetAdrdbydomian(address);

其他

addr _ in . sin _ addr . s _ addr=inet _ addr(地址);

if (connect(sock,(struct sockaddr*)addr_in,sizeof(addr_in) 0)

返回0;

返回1;

}

4.等待连接

客户端使用套接字创建套接字,然后使用连接与其他主机建立连接。作为接收请求的主机服务器,在使用套接字创建套接字之后,有必要绑定所创建的套接字和客户端将请求连接的地址。负责绑定套接字和地址的函数是bind()函数,bind函数的原型如下:

int bind(int sock,const struct sockaddr* addr,sock len _ t addr len);

如果bind函数返回0,则绑定成功。如果返回值为-1,绑定可能会因各种原因而失败。一般如果其他地方没有问题,失败的原因可能是指定地址绑定了其他套接字。以下是绑定函数的应用示例:

/*

绑定指定的内部套接字和指定的内部端口端口号

*/

int BindServerSock(int sock,int port)

{

struct sockaddr _ in sa

sa.sin _ family=AF _ INET

sa.sin_port=htons(端口);

sa . sin _ addr . s _ addr=INADDR _ ANY;

if (bind(sock,(struct sockaddr*)sa,sizeof(sa)) 0)

返回0;

返回1;

}

需要注意的是,sockaddr_in结构的sin_addr变量使用的是INADDR_ANY值,而INADDR_ANY并没有指定具体的值,而是一个任意的值,这样任何一个要连接到具体端口的互联网地址都可以和对应的套接字连接。

使用bind函数将套接字成功绑定到地址后,需要将套接字设置为等待客户端的连接请求。完成此功能的函数是listen()函数,listen函数的原型是:

int listen(int sock,int backlog);

这个函数需要特别注意的是参数积压。当新客户使用连接请求连接时,它将在连接请求被处理之前在积压队列中等待,并且积压被用来确定积压队列的大小。假设该值设置为10,当未处理的连接用户数大于10时,会出现积压队列溢出,然后请求连接的用户出错,给系统网络带来严重的问题。该值优选地根据同时连接的客户端的更大数量来指定积压的值。积压的价值将受到系统更大值的限制。如果大于系统更大值,系统会自动调整到系统更大值,不用太担心。但是,backlog的值不能为0。视操作系统而定,将其设置为0的处理方法可能不同。只能允许一个客户连接,或者不允许一个用户连接。

5.接受连接

当客户端发起连接请求并在积压队列中等待时,需要处理该请求以接受连接并获得与客户端交换数据的套接字。完成该功能的函数是accept()。accept()函数的原型如下:

int accept(int sock,struct sockaddr* addr,sock len _ t * addr len);

accept返回值是在处理新的连接请求后,服务器与客户端通信的套接字的编号。当accept返回-1时,表示新的连接请求没有正常结束。一般accept有问题,可能是因为整个网络有问题或者超过了可以创建的更大套接字数。详细的错误原因可以通过错误得到。以下是接受的应用示例:

/*

接收新的客户端连接

*/

int AcceptNewConnect(int sock)

{

int newSock

struct sockaddr _ in per

socklen _ t perSize

perSize=sizeof(per);

newSock=accept(sock,(struct sockaddr*)per,PerSize);

if(新锁0)

return-1;

返回新闻锁;

}

这个函数的参数int sock指定了要由bind-listen函数处理并等待新连接的套接字,而返回值是与新连接进行数据交换的套接字号,函数失败时返回-1。

6.数据传输

派遣

Send()是通过套接字向另一台主机发送数据的函数。该功能的原型如下:

size_t send(int sock,const char* msg,size_t len,int flags);

发送函数的返回值是发送函数发送的数据长度。使用非阻塞套接字时,可以只发送小于len指定长度的数据。了解TCP/IP的数据传输模式,就容易理解出现这种情况的原因。在TCP/IP中,发送功能发送的数据并不直接发送给对方主机,而是先存储在缓冲区(严格来说,可以看作是通过TCP/IP4协议的一个过程),然后再进行传输。如果系统的缓冲区中没有剩余空间,或者由于其他原因,当调用send函数时,无法保存msg变量,那么阻塞套接字等待直到处理完成,而非阻塞套接字在可能的处理完成后(或者处理本身没有完成)返回错误代码。如果在不知道这个特性的情况下编写socket应用程序,当系统因为客户满了或者系统网络过载而处于异常数据交换时,会遇到很多莫名其妙的bug。虽然这种现象在目前的高配置硬件环境下很少见,但更好还是考虑一下。如果send返回值小于0,说明发送数据的套接字有问题,需要通过error函数检测错误代码。以下是发送的应用示例:

/*

通过发送向对方主机发送数据

*/

int SendData(int sock,const char* buf,int size)

{

int sendSize

sendSize=send(sock,buf,Size,0);

if(发送大小==0)

return-1;

//当在非阻塞模式下出现错误时,

#ifdef EAGAIN

if(误差==EAGAIN)

返回0;

#endif //EAGAIN

#ifdef EWOULDBLOCK

if (ERROR==EWOULDBLOCK)

返回0;

#endif //EWOULDBLOCK

//如果传输的数据与实际要传输的数据不一致,将发送错误

if ((size - sendSize)!=0)

return-1;

返回发送大小;

}

(记得方便保存。这段写了两遍,哭了。)

收到

Recv是当对方主机通过send函数传输的数据保存在系统recv的队列中时,从系统缓冲区中取出数据并存储在程序变量中的函数。recv功能的原型如下所示。

int recv(int s,void* buf,size_t len,int flags);

recv返回值是从系统recv队列中取出并存储在buf变量中的数据字节数。当返回0时,表示对方主机正常断开。当recv函数的返回值小于0时,与位置相关的套接字出现错误。使用Nonblocking socket时,如果error的值是EWOULDBLOCK或者EAGAIN,说明socket本身没有错,但是调用了recv函数,但是因为系统的recv队列是空的,没有数据可以取,所以直接返回recv函数。从系统recv队列中取出并存储在由recv函数的第三个参数len传递的buf变量中的数据长度总是更大的。更大值是指当实际系统的recv队列中有10个字节的数据时,即使len参数设置为1024,recv函数也只取出10个字节的数据,返回值为10。相反,当实际系统的recv队列中有1024字节的数据时,如果len参数设置为10,则只有10字节的数据被取出并存储在buf变量中,而1014字节的数据保留在系统recv队列中。以下是recv函数的应用示例:

/*

数据的接收

*/

int RecvData(int sock,char* recv_buf,int size)

{

int recvSize

recvSize=recv(sock,recv_buf,Size,0);

if (recvSize 0)

返回recvSize

if (recvSize==0)

return-1;

//如果recvSize小于0

#ifdef EINTR

if (ERROR==EINTR)

返回0;

#endif //EINTR

#ifdef EAGAIN

if(误差==EAGAIN)

返回0;

#endif //EAGAIN

#ifdef EWOULDBLOCK

if (ERROR==EWOULDBLOCK)

返回0;

#endif //EWOULDBLOCK

return-1;

}

在非阻塞模式的套接字中,使用recv功能时,要注意以下几点:

send(sock,' abcd ',4,0);

使用send向对端主机发送“abcd”4字节字符串时,如果简单的话,会认为接收数据的主机可以调用recv函数一次,得到4字节的“ABCD”字符串。但实际上recv并不是这样工作的,给编程带来了麻烦。很明显,传输了一个4字节的字符串,但是当主机调用recv函数时,不可能知道以什么顺序传输了多少字节的数据。作为接收方,可以调用recv函数一次,得到4字节的字符串;您也可以调用recv函数两次来获取字符串。因此,由于接收方调用recv功能,很难知道接收的数据是以什么形式传输的。为了屏蔽这种特性,通常的做法是在网络应用程序中建立一个队列或其他数据结构缓冲区,并将recv数据按顺序存储在该缓冲区中,然后根据存储的数据进行分析和使用。这样可以进行完整的数据分析,防止数据丢失。

在UNIX操作系统中,套接字的处理方式可以和文件描述符一样,所以在套接字处理中也可以使用write函数。在套接字处理中,写功能和发送功能本质上是相同的。写功能建模如下:

int write(int sock,void* buf,size _ t len);

它的参数与send函数的参数具有相同的含义。

阅读

send函数和recv函数的关系和write函数和read函数的关系是一样的,也就是说它们的功能是一样的。读取功能建模如下:

int read(int sock,void* buf,size _ t len);

sendto、recvfrom

Sendto和recvfrom函数可以称为UDP函数。UDP函数不同于TCP函数,UDP函数是在服务器中创建socket和accept函数后,可以直接处理下一步而不必调用connect函数的函数。如果sendto是connect和send功能的组合,recvfrom是accept和recv功能的组合,很容易理解这两个功能。这两个函数的原型如下:

int sendto(int s,const char * msg,int len,int flags,const struct sockaddr FAR *to,socket _ t tolen);

int recvfrom(int s,void *buf,int len,int flags,struct sockaddr *from,socket _ t * from len);

在sendto和recvfrom中,除了第五个和第六个参数外,其他参数的含义与send和recv函数相同。但是需要注意的是,与面向连接的TCP不同,UDP调用各个函数时,数据传输是独立处理的。Sendto直接进行传输处理,不经过系统发送队列。当数据报通过sendto函数以A、B、C、D的顺序传输,数据由recvfrom函数接收时,可以按照发送的顺序接收数据,也可以不考虑发送的顺序接收数据(在使用send和recv函数的TCP socket网络应用中,当数据通过一个socket传输时,无论如何都会接收到send函数发送顺序的recv数据)。

  • TAG:
我们已经准备好了,你呢?
2024我们与您携手共赢,为您的企业形象保驾护航