专题 15 TCP套接字编程

来源:互联网 发布:淘宝买东西怎么发国外 编辑:程序博客网 时间:2024/05/16 08:57


      1. 概述

存在三种套接字:流式套接字(SOCK_STREAM)、数据报套接字(SOCK_DGRAM)和原始套接字(SOCK_RAW)

TCP套接字工作流程:

首先,服务器端启动进程,调用Socket创建一个基于TCP协议的流套接字描述符。

其次,服务进程调用bind命名套接字,将套接字描述符绑定到本地地址和本地端口上。

再次,服务器端调用listen,开始侦听客户端的Socket连接请求。

接下来,客户端创建套接字描述符,并且调用connect向服务器端提交连接请求。服务器端接收到客户端连接请求后,调用accept,接受并创建一个新的套接字描述符与客户端建立连接,然后原套接字描述符继续侦听客户端的连接请求。

客户端与服务器端新套接字进行数据传输,调用writesend向对方发送数据,调用readrecv接收数据。

在数据交流完毕后,双方调用close或者shutdown关闭套字。

      1. socket的创建

函数原型:

intsocket(int domain, int type, int protocol);

PS:在实际编程中,我们只使用AF_INET协议,如果需要与本地主机进程建立连接,只需把远程地址设定为127.0.0.1”即可。

创建套接实例:

    1. 创建AF_INET协议族上的流套接字描述符。

socket(AF_INET,SOCK_STREAM, 0)

socket(AF_INET,SOCK_STREAM, TCP)

    1. 创建AF_INET协议族上的数据报套接字描述符。

socket(AF_INET,SOCK_DGRAM, 0)

socket(AF_INET,SOCK_DGRAM, UDP)

      1. socket的命名

函数bind命名一个套接字,它为该套接字描述符分配一个半相关属性,其原型如下:

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

套接字地址属性结构:

structsockaddr

{

u_shortsa_family; /*协议族*/

charsa_data[14] /*最多14字节的协议地址*/

}

每个协议族都定义了自己套接字属性结构,协议族AF_INET使用结构sockaddr_in描述套接字地址信息。

structsockaddr_in

{

shortsin_family; /*16位的地址协议族(AF_INET*/

u_shortsin_port; /*16位的端口地址*/

structin_addr sin_addr; /*32位的IP地址*/

charsin_zero[8]; /*预留,保持sockaddr_in与结构sockaddr的长度相同*/

};

structin_addr

{

u_longs_addr;

}


IP地址转换:

unsignedlong inet_addr(char *ptr);

intinet_aton(char *ptr, struct in_addr *addrptr);

char*inet_ntoa(struct in_addr inaddr);//IP地址的整数形式转换为字符串格式输出


字节顺序转换:

u_longhtonl(u_long hostlong);

u_shorthtons(u_short hostshort);

u_longntohl(u_long netlong);

u_shortntohs(u_short netshort);

h”代表host”,代表主机字节序

n”代表network”,代表网络字节序。

l”代表long”代表32位整数

s”代表short”代表16位整数


例子:命名套接字描述符s的协议为AF_INET,地址由系统自动指定,端口号为1000.

structsockaddr_in sockaddr1; /*申请地址结构空间*/

memset(&sockaddr,0, sizeof(sockaddr)); /*清空空间*/

sockaddr1.sin_family= AF_INET; /*指定为AF_INET协议族*/

sockaddr1.sin_addr.s_addr= htonl(INADDR_ANY); /*任何IP地址*/

sockaddr1.sin_port= htons(10000); /*端口号*/

bind(s,(struct sockaddr *)&sockaddr1, sizeof(sockaddr1));


      1. socket的侦听

函数原型:

intlisten(int s, int backlog);

PS:listen仅在流套接字中使用。上述参数分别表示套接字描述符和套接字接受连接的最大数目。

例:服务器端套接字s进入侦听,最多只能接收客户端的10条申请。

listen(s,10);

/*TCP服务器端套接字准备函数*/

intCreateSock(int *pSock, int nPort, int nMax)

{

structsockaddr_in addrin;

structsockaddr *paddr = (struct sockaddr*)&addrin;

ASSERT(pSock!= NULL && nPort > 0 && nMax > 0);

memset(&addrin,0, sizeof(addrin));


addrin.sin_family= AF_INET;

addrin.sin_addr.s_addr= htonl(INADDR_ANY);

addrin.sin_port= htons(nPort);


ASSERT((*pSock=socket(AF_INET,SOCK_STREAM,0)) > 0);

if(VERIFY(bind(*pSock,paddr,sizeof(addrin))>=0)&& VERIFY(listen(*pSock,nMax)>=0))

return0;

VERIFY(close(*pSock)== 0);

return1;

}

      1. Socket的连接处理

函数原型:

intaccept(int s, struct sockaddr *addr, int *addrlen);

/*TCP服务器端接收连接函数*/

intAcceptSock(int *pSock, int nSock)

{

structsockaddr_in addrin;

intlSize;

ASSERT(pSock!= NULL && nSock > 0);

while(1)

{

lSize= sizeof(addrin);

memset(&addrin,0, sizeof(addrin));

if((*pSock=accept(nSock,(structsockaddr*)&addrin,&lSize))>0)

return0;

elseif(errno == EINTR)

continue;

else

ASSERT(0);

}

}

      1. socket的关闭

函数原型:

intshutdown(int s, int how);

函数shutdown可以全部关闭或者部分关闭套接字描述符s的连接,参数how指定了套接字关闭的方式,取值含义如下:


How取值

描述

0

套接字不可读,系统将自动丢弃接收到的数据和留在缓冲区中的数据,进程不能再从套接字中接收通信数据

1

套接字不可写,系统将写缓冲区的数据发送完毕后关闭套接字写操作,进程不能再从套接字中发送通信数据

2

彻底关闭套接字的连接

shutdown是强制性关闭全部套接字连接,而函数close只将套接字访问计数器减1,当且仅当计数器值为0时,系统才真正地关闭套接字通信。

PS:利用close的这个特性,可以建立Socket通信的服务器端的并发管理:父进程首先创建侦听套接字,一旦接收到连接请求则创建新的套接字与客户连接,再fork子进程,随后父进程调用close关闭新创建的套接字后继续侦听,子进程则调用close关闭侦听套接字,全权负责与客户端的通信。

      1. Socket的连接申请

函数原型:

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

/*********TCP客户端函数**********/

intConnectSock(int *pSock, int nPort, char *pAddr)

{

structsockaddr_in addrin;

longlAddr;

intnSock;

ASSERT(pSock!= NULL && nPort > 0 && pAddr != NULL);

/***创建TCP套接字描述符***/

ASSERT((nSock= socket(AF_INET, SOCK_STREAM, p)) > 0);

/***协议地址组包*****/

memset(&addrin,0, sizeof(addrin));

addrin.sin_family= AF_INET;

addrin.sin_addr.s_addr= inet_addr(pAddr);

addrin.sin_port= htons(nPort);

if(VERIFY(connect(nSock,(struct sockaddr *)&addrin, sizeof(addrin)) > = 0))

{

/***连接成功,返回套接字描述符***/

*pSock= nSock;

return0;

}

close(nSock);

return1;

}

      1. TCP数据的发送

套接字一旦连接上,就可以发送数据。

函数原型:

intsend(int s, const void *msg, int len, int flags);

flags标志:

MSG_OOB:发送外带数据

MSG_DONTROUTE:通知远程IP就在本地局域网内,消息中不加入跌幅消息

      1. TCP数据的接收

函数原型:

intrecv(int s, void *buf, int len, int flags);

flags标志:

MSG_OOB:接收外带数据

MSG_PEEK:以窥视方式接收数据,即只接收而不从缓冲区中删除数据,下一次调用recvread仍然可以接收这些数据

MSG_WAITALL:函数阻塞直到读取len字节数为止。不过本地标志并并非完全阻塞,当进程接收到信号,套接字出错、连接中断或指定了MSG_PEEK等情况出现时函数仍然会提前返回

      1. 其他一些相关函数

structhostent *gethostbyname(const char *name);

structhostent *gethostbyaddr(const char *addr, int len, int type);

voidherror(const char *string);//不能使用perror

structservent *getservbyname(const char *name, const char *proto);

structservent *getservbyport(int port, const char *proto);

intgetsockname(int s, struct sockaddr *name, int *namelen);

intgetpeername(int s, struct sockaddr *name, int *namelen);

      1. 套接字选项

函数原型:

intgetsockopt(int s, int level, int optname, void *optval, int *optlen);

intsetsockopt(int s, int level, int optname, const void *optval, intoptlen);

相关选项说明:

  1. SO_DEBUG

本选项只支持TCP协议,当选项打开时,系统内核跟踪套接字发送和接收的全部TCP数据记录。


  1. SO_REUSEADDRSO_REUSEPORT

当尝试将一个套接绑定到某个端口时,如果该端口已经被占用了,一般情况下,绑定会失败,如果设置了此选项,系统能够让套接字bind到正在使用的地址或端口上。


  1. SO_KEEPALIVE

本选项是周期性地测试套接字连接是否依然存在。

  1. SO_DONTROUT

本选项控制发送消息是否越过协议的路由机制,打开时套接字将绕过协议的路由机制发送的数据。

  1. SO_LINGER

当缓冲区中还有数据尚未发送时,套接字的默认关闭机制是系统将这些数据立即发送对方,但不等待对方确认接收马上关闭套接字。SO_LINGER可以改变这个设置,它的数据传递通过结构linger完成。linger的结构定义如下:

structlinger

{

intl_onoff;//选项开关

intl_linger;//延时时间

}

结构linger成员取值含义如下:

L_onoff

L_linger

关闭机制

0

忽略

默认关闭机制

0

0

直接丢弃缓冲区,立即关关套接字

0

0

等待缓冲区数据发送完毕或者延时l_linger秒后关闭套接字。如果套接字非阻塞选项打开,则立即关闭


  1. SO_BROADCAST

控制能否发送套接字数据广播,只用于数据报套接字和支持广播信息的网络上。

  1. SO_OOBINLINE

此选项打开时允许外带数据留在输入队列中,此时函数readrecv可以在不指明MSG_OOB标志的情况下读取外带数据。

  1. SO_SNDBUFSO_RCVBUF

每个套接都有一个发送缓冲区和接收缓冲区,这两个缓冲区由底层的协议使用,选项SO_SNDBUF可以改变发送缓冲区的大小,选项SO_RCVBUF可以改变接收缓冲区的大小。客户端必须在connect前设置SO_RCVBUF选项,服务器端必须在listen前设置SO_RCVBUF选项,才可以有效地更改缓冲区的容量大小。

  1. SO_SNDLOWATSO_RCVLOWAT

这两个选项分别可以改变最小发送数据量和最小接收数据量。

  1. SO_SNDTIMEOSO_RCVTIMEO

这两个选项用于设置发送数据和接收数据设置一个超时时间。超时时间采用结构timeval描述。

  1. SO_TYPE

调用本选项可以获取套接字的类型,返回值为SOCK_STREAMSOCK_DGRAMSOCK_RAW等,此选项不能用于setsockopt中。

  1. SO_ERROR

调用本选项可以获取并且清除套接字错误,不能应用于函数setsockopt中。



原创粉丝点击