《UNIX网络编程 》学习笔记 (四)

第四章 基本TCP套接字编程
4.1 概述
这一章讲解基本的套接字编程函数。并讲解并发服务器。

4.2 socket 函数 

socket函数建立一个socket套接字

#include <sys/socket.h>

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

返回值:成功返回非负描述符,若出错返回 -1;

int family:协议族 (socket 函数的family常值包括: AF_INET, AF_INET6, AF_LOCAL, AF_ROUTE, AF_KEY).

int type:套接字类型(SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, SOCK_RAM)

int protocol:协议类型(IPPROTO_TCP, IPPROTO_UDP, IPPROTO_SCTP)

(1)不是所有的family 和type的组合都是有效的,所以protocol  参数一般设置为0,让内核自动匹配协议类型,避免错误。

(2)地址族AF_XXX。协议族PF_XXX。

(3)POSIX 规范指定 socket函数的第一个参数为PF_ 值,而AF_值用于套接字地址结构。

4.3 connect 函数 

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

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

 返回值:  成功返回 0 ,出错返回 -1。

int sockfd: socket函数返回的套接字描述符。

const struct *servaddr: 指向套接字地址结构的指针。

socklen_t addrlen: 套接字地址结构的的大小。

(1)传递的套接字地址结构中必须含有服务器的IP地址和端口号。

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

(3)connect 函数将激发TCP的三路握手过程。

(4)connect 发生出错返回的几种情况:

((1))若TCP客户在规定时间内没有收到SYN分节的响应,则返回ETIMEOUT错误。

((2))若对客户的SYN响应的是RST(对方连接复位),则表明服务器主机在我们指定的端口上面没有进程在等待与之连接。这是一种硬错误(hard error),客户一收到RST就马上返回ECONNREFUSED 错误。产生RST分节的三个条件是:目的地为某端口的SYN到达,然而在该端口上没有正在监听的服务器;TCP 想取消一个已有连接;TCP接收到一个根本不存在的连接上的分节。

((3))若客户发出的SYN在某个中间路由器上引发一个“destination unreachable”(目的地不可达)的ICMP错误,则认为是一种软错误(soft error)客户机在内核中保存该消息。并按某种时间间隔继续发送SYN,在规定的时间内仍未收到回应。则把保存的消息作为 EHOSTUNREACH (ENETUNREACH 错误是过时的 ENETUNREACH 作为 EHOSTUNREACH 对待)错误返回给进程。

(5)如果connect  调用失败,则该套接字描述符不能再用,必须关闭,

4.4 bind 函数

bind函数把一个本地协议地址赋予一个套接字,对于TCP/IP协议,协议地址就是IP地址和端口号的组合。

#include <sys/socket.h>

int bind(int sockfd, cosnt struct sockaddr *myaddr, socklen_t addrlen);

返回值:成功返回0,出错返回 -1

int sockfd: 由socket函数返回的套接字描述符。

const struct sockaddr *myaddr: 指向特定于协议的套接字地址结构的指针。

socklen_t : 套接字地址结构的大小。

(1)调用bind函数可以同时指定IP地址和端口号 也可以只指定IP地址或端口号。

(2)除去RPC服务器(使用端口映射器)以外,其他服务器都需要调用bind至少绑定他的服务端口号。

(3)如果客户程序不需要指定特定端口号,那么客户程序一般不调用bind函数,内核将为客户程序分配IP地址和端口号。

(4)如果调用bind函数把一个属于本机某个接口的IP地址绑定到套接字上:对于客户程序来說,这个IP地址就是发送数据报的源IP地址。对于服务器来說,就限制了服务器只能接收目的地为这个IP地址的客户程序的连接。

(5)如果需要绑定的地址被使用 bind 返回一个EADDRINUSE错误。

(6)bind函数并不返回绑定的IP地址和端口号,调用getsockname可以得到这些信息。

(7)bind为不同服务器绑定不同IP ,就可以在同一台多宿主机上运行多个服务器副本,每个副本服务特定的客户。

(8)分组的到达接口和分组的目的地IP地址,强端系统模型和弱端系统哦给模型【P83】。


4.5 listen 函数

listen 函数仅由TCP服务器调用。

#include <sys/socket.h>

int listen(int sockfd, int backlog);

返回值:成功返回0 出错返回 -1。

int sockfd: socket 函数返回的套接字描述符。

int backlog: 已连接套接字的已完成连接的最大排队数。

(1)listen 函数把一个未连接的套接字转换成一个被动套接字,指示内核要接受指向该套接字的连接请求。

(2)内核为每个监听套接字维护两个队列:已完成连接队列(incomplete connection queue)、未完成连接队列(completed connection queue)。

(3)未完成连接队列:客户的SYN已经到达服务器,服务器正在等待三路握手(three-way handshake)完成,这样的连接放入未完成连接队列,套接字处于 SYN_RCVD状态。

(4)已完成连接队列:已经成功完成三路握手(three-way handshake)的连接被放入已完成连接队列。套接字处于 ESTABLISHED状态。

(5)backlog 参数指定的是某个给定套接字上内核为之排队的最大已完成连接数。

(6)对已完成连接数做出限制的目的是:在监听某个给定套接字的应用进程(无论什么原因)停止接受连接的时候,防止内核在该套接字上继续接受新的连接请求。

(7)大量未完成连接队列由内核来处理。

(8)当队列是满的,若有新客户的SYN到达TCP就忽略该SYN,一般并不返回任何出错信息。

4.6 accept 函数

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

#include <sys/socket.h>

int accept(int sockfd,struct sockaddr *cliaddr, socklen_t *addrlen);

返回值:成功返回非负的描述符,出错返回 -1。

int sockfd : 一个监听套接字。

struct sockaddr *cliaddr : 套接字地址结构的指针,用来返回已连接的对端进程的协议地址。

socklen_t *addrlen: 函数调用的时候是传入的套接字地址结构的大小,函数返回时它的值是内核存放在该套接字地址结构中的确切字节数。

(1)如果accept成功返回,那么它将产生一个新的套接字,称为已连接套接字,用来和客户端进行数据传递的正是这个套接字。

(2)一个服务器通常只创建一个监听套接字(listening socket),它在服务器的整个生存周期内都存在。

(3)内核为每个由服务器进程接受的客户连接创建一个已连接套接字,当服务器完成对某个客户的服务时,相应的套接字将被关闭。

(4)accept 函数最多返回三个值:一个既可能是新的套接字描述符也可能是出错指示的整数、客户进程的协议地址、以及该地址的大小。

4.7  fork 函数

fork函数是UNIX 中派生新进程的唯一方法。

#include <unistd.h>

pid_t fork(void);

返回值:在子进程中返回0,在父进程中返回子进程的ID,出错返回 -1

(1)调用一次fork函数,将在父进程和子进程中各返回一次,在父进程中(即调用进程)返回创建的子进程的ID,在子进程中返回0,因此,通过查看返回值就能确定当前进程是父进程还是子进程。

(2)fork 在子进程中返回0,在父进程中返回子进程的ID号的原因在于:一个子进程只有一个父进程,而且在子进程中可以通过调用getppid获取父进程ID。但是父进程可以有多个子进程,并且在父进程中没有办法获取子进程的ID,如果 父进程想跟踪子进程,那么它必须在fork返回后保存子进程的ID。

(3)父进程在调用fork之前打开的所有描述符在fork返回后由子进程共享(被复制一份到子进程空间)。

(4)fork函数的2个典型用法:  ((1))一个进程创建一个自身的副本,每个副本执行各自的操作。((2))一个进程想要执行另外一个程序,那么它先调用fork函数创建一个自身的副本,然后调用exec函数把自身替换成新的程序。

(5)存放在硬盘上的可执行程序能够被UNIX执行的唯一方法是:由一个现有进程调用六个exec函数中的某一个来转载这个新程序。

(6)六个exec函数的具体讲解(P91).

4.8 并发服务器

创建一个监听套接字,并在这个监听套接字上调用accept 。当accept返回一个已连接套接字描述符时,调用fork函数创建一个子进程,让子进程和客户保持连接并处理客户的全部请求。这就是多进程并发服务器的基本模式。

4.9 close 函数

通常的UNIX close 函数也用来关闭套接字,并终止TCP连接。

#include <inistd.h>

int close(int sockfd); 

返回值:成功返回0,出错返回 -1;

(1)close 一个TCP套接字的默认行为是把该套接字设置成已关闭,然后立即返回到调用进程。

(2)在并发服务器中,fork一个子进程会复制父进程在fork之前创建的所有描述符,复制完成后相应描述符的引用计数会增加1。

(3)调用close 会使描述符的引用计数减1,一旦描述符的引用计数为0,内核就会关闭该套接字。

(4)调用close后套接字的描述符引用计数仍然大于0的话,就不会引发TCP的终止序列。如果想在一个TCP连接上发送FIN 可以调用shutdown函数。

4.10 getsockname 和 getpeername 函数

getsockname 返回套接字的本地协议地址(地址族、IP地址、端口号)。getpeername返回套接字对端的协议地址(地址族、IP地址、端口号)

#include <sys/socket.h>

int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);

int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);

返回值:成功返回0,出错返回-1.

int sockfd: socket函数返回的套接字描述符。

struct sockaddr *localaddr/peeraddr : 套接字地址结构的指针。

socklen_t: 套接字地址结构的大小。

4.11 小结

(1)所有的客户服务器程序都是从调用socket函数开始。

(2)客户程序的调用顺序一般是  socket --->connect ---->process user input;

(3)服务器的调用顺序一般是:socket--->bind--->listen--->accept--->process user input;

(4)并发服务器为每个客户连接创建一个进程或者线程来处理客户的请求。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值