Linux网络编程(二) 基本TCP套接字编程

来源:互联网 发布:服务器上如何绑定域名 编辑:程序博客网 时间:2024/06/08 06:49

1.socket函数

为了执行网络IO,一个进程必须先调用socket函数,指定期望通信的协议类型

  1: #include<sys/socket.h>
  2: int socket(int family,int type,int protocol);
  3: //返回:成功返回非负描述符,若出错返回-1

family参数的值

AF_INET      IPv4协议

AF_INET6    IPv6协议

type参数的值

SOCK_STREAM    字节流套接字

SOCK_DGRAM     数据报套接字

protocol参数的值

IPPROTO_TCP       TCP传输协议

IPPROTO_UDP       UDP传输协议

2.connect函数

TCP客户利用connect函数建立于TCP服务端的连接

  1: #include<sys/socket.h>
  2: int connect(int socked,const struct sockaddr *servaddr,socklen_t addrlen);
  3: //返回:若成功则为0,出错为-1

sockfd是由socket函数返回的描述符,第二个和第三个参数分别是指向套接字地址结构的指针和该结构的大小。

客户在调用函数connect前不必非得调用bind函数,因为如果需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口

调用connect函数将激发TCP的三路握手过程,而且仅在建立成功后或出错才返回,其中出错可能有以下几种情况

(1)若TCP没有收到SYN分节的相应,则返回ETIMEOUT错误。(调用connect函数时,内核发送一个SYN,若无响应,则等待6秒后再发送,仍无响应则等大气24秒,总共等待75秒后无响应返回错误)

(2)若对客户的SYN的响应是RST分节,则表明服务端主机在我们指定的端口没有进程正在等待与之连接。这是一种硬错误,客户一接收到RST就马上返回ECONNREFUSED错误

(3)若客户发出的SYN在中间的某个路由器上引发了目的不可达ICMP错误,则认为是一种软错误。客户主机内核保存该消息,并按第一种情况中所述的时间间隔发送SYN。若在某个规定的时间后仍未收到响应(一般为75秒),则把保存的消息作为EHOSTUNREACH或ENETUNREACH错误返回个进程。

下图显示connect后激发三次握手过程

image

connect函数将当前套接字从CLOSED状态转换为SYN_SENT状态,若成功则再装换到ESTABLISHED状态。若失败则套接字不再可用,必须关闭,我们不能对这样的套接字再次调用connect

3.bind函数

bind函数将一个本地协议地址赋予一个套接字。

  1: #include<sys/socket.h>
  2: int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
  3: //返回:若成功返回0,出错返回-1

4.listen函数

listen函数仅由TCP服务器调用,它做两件事

(1)当socket创建一个套接字时,他被假设为一个主动套接字,也就是说它是一个调用connect函数发起连接的客户端套接字。listen函数将一个未连接的套接字转换成一个被动套接字。调用listen导致套接字从CLOSED状态转换到LISTEN状态

(2)本函数的第二个参数指定了内核为相应套接字排队的最大连接数

  1: #include<sys/socket.h>
  2: int listen(int sockfd,int backlog);
  3: //若成功返回0,若出错返回-1

内核为每一个给定的监听套接字维护两个队列:
(1)未完成连接队列:每个这样的SYN分节对应一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RCVD状态

(2)已完成连接队列:每个已完成TCP三次握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态

下图描绘了上述的两个队列

image

 

当来自客户的SYN到达时,TCP在未完成连接队列中创建一个新项,然后响应以三路握手的第二个分节:服务器的SYN响应,其中捎带对客户SYN的ACK。这一项一直保存在未完成连接队列中,直到三路握手的第三个分节(客户对服务器SYN的ACK)到达或者超时为止。

如果三路握手正常完成,该项就从未完成队列移到已完成连接队列的队尾。当进程调用accept时,已完成队列中的队头返回给进程,如果进程为空,那么进程投入睡眠,直到TCP在该队列中放入一项才唤醒它。

在三路握手正常完成的前提下,未完成连接队列中的任意一项在其中存留的时间就是一个RTT

当一个SYN到达时,若这些队列是满的,TCP就忽略这个分节,不发送RST。这么做是因为:要是服务器返回一个RST,客户的connect调用就会立即返回一个错误,而不是TCP正常重传机制来处理

在三路握手完成后,但在服务器调用accept之前到达的数据应该由服务器TCP排队,最大数据量为相应已连接套接字的接收缓冲区大小

5.accept函数

accept函数有TCP服务器调用,用于从已完成连接队头返回一个已完成连接。如果队列为空,那么进程被投入睡眠。

  1: #include<sys/socket.h>
  2: int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
  3: //返回:若成功为非负描述符,若出错为-1

返回值为已连接套接字,第一个参数为监听套接字

6.getsockname和getpeername函数

这两个函数返回与某个套接字关联的本地协议地址,或者返回与某个套接字关联的外地协议地址

  1: #include<sys/socket.h>
  2: int getsockname(int sockfd,struct sockaddr *localaddr,socklen_t *addrlen);
  3: int getpeername(int sockfd,struct sockaddr *peeraddr,socklen_t *addrlen);
  4: //返回:若成功返回0,若出错则为-1

第一个参数为套接字描述符,必须是已连接套接字描述符,而不是监听套接字描述符

7.并发服务器

一个基本的并发服务器模型最简单的方法就是fork一个子进程来服务每个客户

  1: pid_t pid;
  2: int listenfd,connfd;
  3: listenfd=socket();
  4: 
  5: bind(listenfd,....);
  6: listen(listenfd,LISTENQ);
  7: for(;;)
  8:   {
  9:    connfd=accept(listenfd,...);
 10:    if(pid=fork()==0)
 11:    {
 12:         close(listenfd);//child closes listening socket
 13:         doit(connfd);   //process the request
 14:         close(connfd);  //done with this client
 15:         exit(0);
 16:   }
 17: close(connfd);   //parent closes connectfd socket
 18: }

如果父进程每个由accept返回的已连接套接字都不调用close

首先,父进程将耗尽可用的描述符,因为在任何时刻可拥有的打开着的描述符数通常是有限制的。

而且,没有一个客户将会被终止。当子进程关闭已连接套接字时,它的引用计数将由2递减为1且保持为1,因为父进程不关闭任何已连接套接字,这将妨碍TCP连接终止序列的发生,导致连接一直打开着。

0 0
原创粉丝点击