3、UNIX网络编程学习笔记--TCP套接字编程

一、典型时间表

1、下面是一对TCP客户与服务器进程之间发生的一些典型事件的时间表。

2、服务器首先启动,稍后某个时刻客户启动,它试图连接到服务器。我们假设客户给服务器发送一个请求,服务器处理该请求,并且给客户发回一个响应。这个过程一直持续下去,直到客户关闭连接的客户端,从而给服务器发送一个EOF(文件结束)通知为止。服务器接着也关闭连接的服务器端,然后结束运行或者等待新的客户连接。


3、类比电话系统



二、核心API介绍

1、socket函数

(1)为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型(使用IPv4的TCP、使用IPv6的UDP、Unix域字节流协议等)

//
#include <sys/socket.h>
int socket(int family, int type, int protocol);
//

(2)返回值:若成功则为非负描述符,若出错则为-1。socket函数在成功时返回一个小的非负整数值,它与文件描述符类似,我们把它称为套接字描述符(socket descriptor),简称sockfd

(3)参数:

    A、family参数指明协议族,family参数指明协议族,该参数往往被称为协议域,其对应下图中某个常值。

    B、type参数指明套接字类型,其对应下图中某个常值。

    C、protocol参数应设为下图中某个协议类型常值。也可以设为0,如果为0,系统会根据family和type组合选择系统默认值。




2、connect函数

(1)TCP客户用connect函数来建立与TCP服务器的连接

//
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socket_t addrlen);
//
(2)返回值:若成功则为0,若出错则为-1。
(3)参数:
    A、sockfd是由socket函数返回的套接字描述符。
    B、servaddr 指的是一个指向 套接字地址结构的指针。

    C、addrlen 指的是套接字地址结构servaddr的大小。

(4)套接字地址结构必须含有服务器的IP地址和端口号。客户端在调用connect函数前不必非得调用bind函数,因为如果需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口。

(5)如果是TCP套接字,调用connect函数将激发TCP三次握手,而且仅在连接建立成功或出错时才返回,出错返回可能如下:

    A、若TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT错误。举例,调用connect函数时,4.4BSD内核发送一个SYN,若无响应则等待6s后再发送一个,若仍无响应则等待24s后再发送一个。若总共等待75s后仍无响应则返回本错误。

    B、若对客户的SYN的响应是RST(复位),表明该服务器主机在我们指定的端口上没有进程在等待与之连接。这是一种硬错误(hard error),客户一接收到RST就马上返回ECONNREFUSED错误。

    C、若客户发出的SYN在中间的某个路由上引发了一个“destination unreachable”(目的地不可达)ICMP错误,则认为是一种软错误(soft error)。客户主机内核保存该消息,并按第一种情况所述的时间间隔继续发送SYN。若在75s(4.4BSD内核规定)后仍未收到响应,则把保存的消息(即ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。

3、bind函数

(1)bind函数把一个本地协议地址赋予一个套接字。对于网络协议,协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCP或UDP端口号的组合。

//
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
//

(2)返回值:若成功则为0,若出错则为-1.

(3)参数:

    A、sockfd是由socket函数返回的套接字描述符。

    B、myaddr 指的是一个指向特定于协议的地址结构的指针。

    C、addrlen 指的是该地址结构的长度。

(4)对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。

4、listen函数

(1)当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的客户套接字。listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。即调用listen导致套接字从CLOSED状态转换到LISTEN状态

//
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//

(2)返回值:若成功则为0,若出错则为-1.

(3)参数:

    A、sockfd是由socket函数返回的套接字描述符。

    B、backlog 后续做解释。

5、accept函数

(1)accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠(假定套接字为默认的阻塞方式)。

//
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
//

(2)返回值:若成功则为非负描述符,若出错则为-1.

(3)参数:

    A、sockfd是由socket函数返回的套接字描述符。

    B、cliaddr (可选)指针,用来返回已连接的对端进程(客户)的协议地址。

    C、addrlen (可选)指针,配合cliaddr一起使用,指向存有cliaddr地址长度的整型数。

(4)如果accept成功,那么其返回值是由内核自动生成的一个 全新描述符,代表与返回客户的TCP连接

6、fork和exec函数

    fork函数:

(1)unix下的fork函数,该函数(包括有些系统可能系统它的各种变体)是Unix中派生新进程的唯一办法。

//
#include <unistd.h>
pid_t fork(void);
//

(2)返回值:在子进程中为0,在父进程中为子进程ID,若出错则为-1.

(3)调用fork函数一次,会返回两次。它在调用进程(称为父进程)中返回一次,返回值是新派生进程(称为子进程)的进程ID号;在子进程又返回一次,返回值为0。因此,返回值本身告知当前进程一个是子进程还是父进程。

(4)fork在子进程返回0而不是父进程的进程ID的原因在于:任何子进程只有一个父进程,而且子进程总是可以通过getppid获取父进程的进程ID。相反,父进程可以有许多子进程,而且无法获取各个子进程的进程ID。如果父进程想要跟踪所有子进程的进程ID,那么它必须记录每次调用fork的返回值。父进程中调用fork之前打开的所有描述符在fork返回之后由子进程分享。网络服务器有利用这个特性:父进程调用accept之后调用fork。所接受的已连接套接字随后就在父进程与子进程之间共享。通常情况下,子进程接着读写这个已连接套接字,父进程则关闭这个已连接套接字。

(5)fork有两个典型用法:

    A、一个进程创建一个自身的副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的某个操作。这是网络服务器的典型用法。

    B、一个进程想要执行另一个程序。既然创建新进程的唯一办法是调用fork,该进程于是首先调用fork创建一个自身的副本,然后其中一个副本(通常为子进程)调用exec把自身替换成新的程序。这是诸如shell之类程序的典型用法。

7、并发服务器

(1)当服务一个客户请求可能花费较长时间时,我们并不希望整个服务被单个客户长期占用,而是希望同时服务多个客户。Unix中编写并发服务器程序最简单的办法就是fork一个子进程来服务每个客户。典型并发服务器程序如下:


8、close函数

(1)通常Unix close 函数也用来关闭套接字,并终止TCP连接。

//
#include <unistd.d>
int close(int sockfd);
//

(2)返回值:若成功则为0,若出错则为-1.

(3)close一个TCP套接字的默认行为是把该套接字标记成已关闭,然后立即返回到调用进程。 该套接字描述符不能再由调用进程使用,也就是说它不能再作为read或write的第一个参数。然而TCP将尝试已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列。

9、getsockname和getpeername函数

(1)getsockname和getpeername函数这两个函数或者返回与某个套接字关联的本地协议地址(getsockname),或者返回与某个套接字关联的外地协议地址(getpeername).

//
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *locoladdr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
//

(2)返回值:若成功则为0,若出错则为-1.


致谢

1、《UNIX网络编程》卷一:套接字联网API  [第3版]  W.Richard Stevens、Bill Fenner、Andrew M.Rudoff 著

2、TCP网络编程中connect()、listen()和accept()三者之间的关系




猜你喜欢

转载自blog.csdn.net/qq_38880380/article/details/80332363
今日推荐