unix网络编程笔记(二)

第四章笔记

1. 基本Tcp客户端/服务器程序的套接字函数

2. socket函数:

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

(1)socket有三个函数,除了tcp udp外还支持许多协议。

(2)对于tcp协议:三个参数分别为AF_INET/AF_INET6、SOCK_STREAM、0

(3)对于udp协议:三个参数分别为AF_INET/AF_INET6、SOCK_DGRAM、0

(4)其他协议和参数含义,先pass

3. connect函数:

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

(1)正如第三章所示:第二个参数是通用的套接字地址结构指针,但是传入参数时需要指定具体的套接字地址结构

(2)客户在调用connect前不必非得调用bind函数,因为内核对确定源IP地址,并选择一个临时端口作为源端口

(3)connect激发Tcp三次握手过程。

(4)connect可能出错的情况:

a: 若客户机与服务器断连,Tcp客户端没有收到SYN分节的响应,返回ETIMEDOUT错误。举例:对于4.4BSD,当内核发送SYN,若无响应6s后再发送,若仍无响应再24s后再发送,若总共等了75s仍未响应返回ETIMEDOUT

经过测试,运行time intro/daytimetcpcli 10.0.0.1,经过2m7s左右才返回connect time out的错误

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

c: 当客户发出的SYN在中间的某个路由器引发了一个目的地不可达的ICMP错误,这认为是一种软错误,之后按第一种情况所属的时间间隔继续发SYN。若在某个规定时间仍未收到响应,则返回EHOSTUNREACH或ENETUNREACH错误。

(4)当connect成功返回,当前套接字进入ESTABLISHED状态,即三次握手成功状态。若失败则该套接字不再使用,如果需要再次调用connect时,必须close当前的套接字都重新调用socket

4. bind函数:

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

(1)绑定的IP地址和端口号,可以指定通配IP地址和端口号0。如果指定端口号为0,那么内核就在bind被调用时选择一个临时端口。如果指定IP地址为通配地址,那么内核将等到套接字已连接(TCP)或已在套接字上发出数据报(UPD)时才选择一个本地IP地址。

(2)对于IPv4,通配地址由常量INADDR_ANY(0)来指定。但是IPv6的IP地址是128位,不是简单类型,不能像IPv4那样用简单数值常量表示。

IP地址用通配地址赋值代码:

struct sockaddr_in saddr;
saddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY定义在头文件<netinet/in.h>
struct sockaddr_in6 saddr6;
saddr6.sin6_addr = in6addr_any;//系统预先分配in6addr_any变量并将其初始化常量IN6ADDR_ANY_INIT。头文件<netinet/in.h>含有in6addr_any的extern声明。

(3)传入bind的套接字地址IP和端口号不要忘记转换成网络字节序

(4)如果bind的是一个临时端口号,由于bind并不返回所选择的值,那么我们无法知道究竟bind了哪个端口号,可以调用函数getsockname来返回协议地址。

(5)bind返回的常见错误EADDRINUSE(地址已使用)

当绑定内置端口号(1-1024),必须有root权限,否则bind返回Permission denied错误。

5. listen:

int listen(int sockfd,int backlog)

(1)backlog的含义:内核为监听套接字维护的已完成连接队列(以完成三次握手,状态为ESTABLISHED,并正等待accpet)总个数的最大值

(2)backlog到底设置多少是合理的呢????

(3)当一个客户SYN到达时,若这些队列是满的,TCP就忽略该分节,而不是立即响应RST。因为这种情况是暂时的,客户端会等一段时间会重发SYN,期望不久能在这些队列中找到可用空间。

6. accept:

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

(1)作用:用于从已完成连接队列返回下一个已完成连接。如果已完成连接队列为空,那么进程被进入睡眠(如果套接字为默认的阻塞方式)。

(2)如果对返回的套接字地址不感兴趣,cliaddr和addrlen可以设置为NULL

(3)accept返回的cliaddr,IP地址和端口号都是网络字节序,如果要打印出来查看,需要先转换成主机字节序,之后端口号可以直接打印,IP地址之后需要再次调用inet_ntop来获取字符串格式

6. close:

int close(int sockfd);

(1)作用:将套接字句柄引用计数减1,如果引用计数降为0,则将套接字标记为关闭,并立即返回到调用进程。而Tcp协议栈则尝试发送已排序等待发送到对端的任何数据,之后发送FIN分节,接收端(协议栈)收到后传递给应用程序一个文件结束符。之后接受端发送ACK和FIN。之后发送端再次发送ACK。

(2)标记为关闭的套接字之后不能再被进程调用,既不能再用于read和write

7. shutdown:

shutdown没有句柄引用计数的概念,它的作用是立即向对端发送FIN,并且shutdown之后的套接字只不能用于写,但可用于读

8. getsockname getpeername

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

(1)getsockname:返回与某个套接字关联的本地协议地址(IP地址和端口号和地址族)

(2)getpeername:返回与某个套接字关联的对端协议地址(IP地址和端口号和地址族)

(3)需要这两个函数的理由:

a. 在没有调用bind或以端口号0调用bind的客户程序中,connect成功返回后,getsockname用于返回由内核赋予该连接的本地IP地址和端口号。

b. 用通配IP地址调用bind的服务器程序中,getsockname用于返回内核赋予该连接的本地IP地址。注意:传入的套接字描述符必须是已连接套接字描述符,而不是监听套接字描述符

c. 如果不知道传入的套接字地址具体是哪个地址族,可以传入sockaddr_storage,该结构能承载系统支持的任何套接字地址结构的空间大小。

int sockfd_to_family(int sockfd)
{
    struct sockaddr_storage ss;
    socklen_t len;
    len = sizeof(ss);
    if(getsockname(sockfd,(sockaddr*)&ss,&len) < 0;
        return -1;
    return (ss.ss_family);
}

9. 并发服务器

(1)fork返回值>0的,表示当前进程是父进程,fork返回的是子进程的pid。fork返回值=0的,表示当前进程是子进程,子进程可调用getppid获取父进程的pid。

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

(3) 实现过程:当通过accept获取一个客户请求,然后fork一个子进程,子进程首先关闭监听监听套接字,并处理客户请求。父进程则关闭连接套接字,并再次调用accept获取下一个客户请求。

(4)使用fork对关闭套接字的处理:父进程中调用fork之前打开的所有描述符在fork返回之后由子进程分享。父进程调用accept之后调用fork,所接受的监听套接字和已连接套接字则由父子进程共享。通常,子进程关闭监听套接字,接着读写这个已连接套接字;父进程不要忘记关闭这个已连接套接字。原因见下面注释。

(5)fork的子进程处理完连接套接字描述符后,不要忘记调用exit退出进程,否则会执行到父进程的代码。

(5)典型的并发服务器程序轮廓:

pid_t pid;
int listenfd,connfd;
listenfd = Socket(...);
Bind(listenfd, ...);
Listen(listenfd,LISTENQ);
for(;;)
{
    connfd = Accept(listenfd, ... );
    if( (pid = Fork()) == 0)
    {
         Close(listenfd);  //因为exit会终止进程,而进程终止处理的部分工作就是关闭所有由内核打开的描述符,所有close listenfd可写可不写
         doit(connfd);
         Close(connfd);
         exit(0);  //不要忘记调用exit,来关闭该进程
     }
     Close(connfd);   //不要忘记close connfd,因为父进程不会用到connfd, close connfd不一定会真的关闭进程,它只是把进程的引用计数减一。如果父进程不关闭connfd,即使子进程close connfd也不会真正关闭connfd,导致连接一直打开着。而且这将导致父进程耗尽可用描述符,因为任何进程在任何时刻可拥有的打开着的描述符通常是有限的。
}

10. 总结:

(1)传入bind的套接字地址IP和端口号不要忘记转换成网络字节序

(2)bind指定的IP地址可以为通配IP地址,表示内核将等到套接字已连接(TCP)或已在套接字上发出数据报(UPD)时才选择一个本地IP地址。对于IPv4用INADDR_ANY指定,对于IPv6用in6addr_any指定。bind指定的端口号可以为0,表示内核自己分配端口

(3)若connect失败后则该套接字不再使用,如果需要再次调用connect时,必须close当前的套接字都重新调用socket

(4)accept返回的套接字地址是网络字节序,如果对accept返回的套接字地址不感兴趣,cliaddr和addrlen可以设置为NULL;如果需要读取accept返回的套接字地址,需要先从网络字节序转换从主机字节序

(5)close只是将套接字句柄引用计数减1,如果引用计数降为0,才将套接字标记为关闭,之后进程不能再对close的套接字进行任何调用

(6)对套接字标记为关闭后,Tcp协议栈则尝试发送已排序等待发送到对端的任何数据,之后发送FIN分节,接收端(协议栈)收到FIN后传递给应用程序一个文件结束符通知应用程序收到了对端的FIN,之后两端发送的网络终止序列不在累述

(7)getsockname和getpeername用于返回与某个套接字关联的本地和对端协议地址(IP地址 端口号 地址族)

(8)使用fork实现并发服务器:子进程不要忘记关闭监听套接字,接着读写这个已连接套接字;父进程不要关闭这个已连接套接字;fork的子进程处理完连接套接字描述符后,不要忘记调用exit退出进程,否则会执行到父进程的代码。典型的并发服务器程序轮廓见9.(5)

时间: 2024-10-24 13:00:41

unix网络编程笔记(二)的相关文章

【UNIX网络编程(二)】基本TCP套接字编程函数

基于TCP客户/服务器程序的套接字函数图如下: 执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型. #include <sys/socket.h> int socket(int family, int type, int protocol);/*返回值:若成功则为非负描述符,若出错则为-1*/ socket函数成功时返回一个小的非负整数值,它与文件描述符类似,把它称为套接字描述符,简称sockfd.family参数指明协议族,被称为协议域.type参数指

UNIX网络编程笔记(1)—传输层协议

开始学习网络编程的经典<UNIX网络编程>(第3版)作为研究生阶段的副本练习吧,厚厚一本书,希望能坚持看下去,坚持做些笔记. 1.TCP/IP协议概述 IPv4 网际协议版本4(Internet Protocol version 4),32位地址,为TCP.UDP.SCTP.ICMP和IGMP提供分组递送服务. IPv6 网际协议版本6(Internet Protocol version 6).128位地址,为TCP.UDP.SCTP和ICMPv6提供分组递送服务. TCP 传输控制协议(Tr

UNIX网络编程笔记(4)—TCP客户/服务器程序示例

TCP客户/服务器程序示例 这一章信息量开始大起来了,粗略来看它实现了简单的TCP客户/服务器程序,里面也有一些费解的细节. 1.概述 完整的TCP客户/服务器程序示例.这个简单的例子将执行如下步骤的一个回射服务器(这里的回射服务器就是服务简单的把客户端发送的消息返回给客户): 1)客户从标准输入读入一行文本,并写给服务器 2)服务器从网络输入读入这行文本,并回射给客户 3)客户从网络输入读入这行回射文本,并显示在标准输出上 这样实际上就构成了一个全双工的TCP连接. 本章就围绕了这个简单的TC

UNIX网络编程笔记(2)—套接字编程简介

套接字编程概述 说到网络编程一定都离不开套接字,以前用起来的时候大多靠记下来它的用法,这一次希望能理解一些更底层的东西,当然这些都是网络编程的基础- (1)套接字地址结构 大多说套接字函数都需要一个指向套接字地址结构的指针作为参数,每个协议族都定义它自己的套接字地址结构,这些结构都以sockadd_开头. IPV4套接字地址结构 IPv4套接字地址结构通常称为"网际套接字地址结构",以sockaddr_in命名,并定义在 /* Internet address. */ typedef

unix网络编程笔记(一)

第三章笔记 1. 套接字地址: 1.1 分类: 套接字地址分两种,一种是通用套接字地址类型struct sockaddr,它只是为了在套接字函数中可以传入任意套接字地址而定义的结构体,功能类似于void*,而出现定义套接字函数时还没有出现void*,所以才定义了该结构体.sockaddr 使用unsigned short int sa_family 来表明具体地址类型.另一种是具体协议的套接字地址类型,如果需要具体的套接字地址类型,需要使用sockaddr_in(IPv4)或sockaddr_i

UNIX 网络编程笔记-CH3:套接字编程简介

IPv4套接字地址结构 struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { uint8_t sin_len; /* length of structure (16) bytes */ sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* 16-bit TCP/UDP port, network byte order(big-endian) */ struct in

UNIX网络编程笔记(3)—基本TCP套接字编程

基本TCP套接字编程 主要介绍一个完整的TCP客户/服务器程序需要的基本套接字函数. 1.概述 在整个TCP客户/服务程序中,用到的函数就那么几个,其整体框图如下: 2.socket函数 为了执行网络I/O,一个进程必须要做的事情就是调用socket函数.其函数声明如下: #include <sys/socket.h> int socket(int family ,int type, int protocol); 其中: family:指定协议族 type:指定套接字类型 protocol:指

UNIX网络编程笔记(5)—I/O复用select/poll

I/O复用:select和poll函数 1. 概述 考虑一种情况,当客户端阻塞于fgets调用时,服务器进程被杀死:此时服务器TCP虽然正确地给客户TCP发送了一个FIN,但是由于客户进程阻塞于标准输入的过程,直到从套接字读时为止.这样的进程就需要一种机制,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程.这个能力就叫做I/O复用.由select和poll函数支持的. I/O复用典型使用在下列网络应用场合: (1)客户处理多个描述符. (2) 客户通知处理多个套接字. (3) 服务

UNIX网络编程笔记(6)—UDP网络编程

基本UDP套接字编程 1. 概述 TCP和UDP的本质区别就在于:UDP是无连接不可靠的数据报协议,TCP是面向连接的可靠字节流.因此使用TCP和UDP编写的应用程序存在一些差异.使用UDP编写的一些常见的应用程序有:DNS(域名解析系统).NFS(网络文件系统)和SNMP(简单网络管理协议). 2. sendto和recvfrom函数 类似与标准的read和write函数: #include <sys/socket.h> ssize_t recvfrom (int sockfd,void *