unp第三章主要介绍了基本套接字编程函数。主要有:socket(),bind(),connect(),accept(),listen()等。

本博文也直接进入正题,对这几个函数进行剖析和讲解。

1. 基本套接字函数

在《计算机网络》和《TCP/IP详解》中,我们经常讨论TCP/IP的工作流程,连接建立的三次握手和连接断开的四次挥手等,那么这些如何体现在程序中呢?我们如何来运用这些理论知识于实践之中呢?下面我们来看看套接字编程中客户和服务器进程之间的一些典型事件的时间表。

如图,服务器首先启动,稍后客户进程启动,它通过connect()函数试图连接服务器,这个阶段完成三次握手,然后read()和write()完成客户和服务器之间的数据传输,之后客户进程调用close()来请求断开连接,服务器收到后读取EOF,接着关闭连接,这时完成四次挥手的过程。下面就图中的每个函数,细细剖析他们的用途。

2. socket函数

为了执行网路I/O,进程做的第一件事情就是调用socket()函数,指定期望的通信协议类型。

#include <sys/socket.h>
/*  family --指明协议簇  */
/*  type --指明套接字类型  */
/*  protocol --指明使用那个协议(当此项为0时,则根据family和type组合的系统默认值)  */
int socket (int family, int type, int protocol);//若成功则返回非负描述符sockfd,若出错则返回-1

下表为他们的一些取值:

family 说明 type 说明 protocol 说明
AF_INET IPv4协议 SOCK_STREAM 字节流套接字 IPPROTO_TCP TCP传输协议
AF_INET6 IPv6协议 SOCK_DGRAM 数据报套接字 IPPROTO_UDP UDP传输协议
AF_LOCAL Unix域协议 SOCK_SEQPACKET 有序数组套接字 IPPROTO_SCTP SCTP传输协议
AF_ROUTE 路由套接字 SOCK_RAW 原始套接字
AF_KEY 密钥套接字

当然,这些参数不能随便设置的,下表给出了一些有效的组合和对应真正的协议

Protocol AF_INET AF_INET6 AF_LOCAL AF_ROUTE AF_KEY
SOCK_STREAM TCP/SCTP TCP/SCTP
SOCK_DGRAM UDP UDP
SOCK_SEQPACKET SCTP SCTP
SOCK_RAW IPv4 IPv6

3. connect函数

client客户进程调用此函数来建立与服务器间的连接。

#include <sys/socket.h>
/*  sockfd --socket()函数返回的套接字描述符  */
/*  servaddr --指向套接字地址结构的指针  */
/*  addrlen --该结构的大小  */
int connect (int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);//若成功返回0,反之返回-1

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

调用connect()函数,出错的情况有如下几种:

  • 若TCP客户没有接收到SYN分节的响应,则返回ETIMEOUT错误。

  • 若对客户的响应时RST(复位),则表明该服务器主机在我们制定的端口上没有进程在等待与其连接,会返回ECONNREFUSED。如服务器进程也许没有运行的情况。(硬错误)

RST是TCP在发生错误时发送的一个TCP分节。产生RST的三个条件:目的地为某端口的SYN到达,然而在该端口没有正在监听的服务器;TCP想取消一个已有连接;TCP接收到一个根本不存在的连接上的分节。

  • 若客户发出的SYN在中间某个路由器上引发“目的地不可达的ICMP错误”,则认为时一种“软错误”,按照TCP协议的规定,客户会按照一定的时间间隔重发SYN,若在某个规定时间内仍未收到响应,则返回EHOSTUNREACH和ENETUNREACH错误。

4. bind函数

用于把一个本地协议地址赋予一个套接字。对于网际网协议,协议地址时32位的IPv4地址或者128位IPv6地址与16位的TCP或UDP端口号的组合。调用bind可以制定IP地址或端口,也可以两者都指定,也可以都不指定。

#include <sys/socket.h>
/*  sockfd --socket()函数返回的套接字描述符  */
/*  servaddr --指向套接字地址结构的指针  */
/*  addrlen --该结构的大小  */
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);//若成功返回0,反之返回-1

5. listen函数

该函数主要干两件事情

(1) 当socket函数创建一个套接字时,它被假设为一个主动套接字(将调用connect发起连接的客户套接字)

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

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

#include <sys/socket.h>
/*  sockfd --socket()函数返回的套接字描述符  */
/*  backlog --内核为相应套接字排队的最大连接个数  */
int listen(int sockfd, int backlog);//若成功返回0,反之返回-1

6. accept函数

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

/*  sockfd --socket()函数返回的套接字描述符  */
/*  cliaddr --用来返回已连接的对端进程的协议地址  */
/*  addrlen --调用前:为cliaddr所指的套接字地址结构大小  */
/*          --调用后:为由内核存放在该套接字地址结构内的确切字节数  */
int accept(int sockfd, struct sockaddr *cliaddr , socklen_t *addrlen);//若成功返回非负描述符,若出错,则返回-1

如果accept调用成功,则返回一个由内核自动生成的全新描述符,代表与所返回客户的TCP连接。

accept的参数socket通常被称为监听套接字描述符,而其返回值为连接套接字描述符。一个服务器通常只创建一个监听套接字描述符,内核为每个由服务器进程接受的客户创建一个已连接套接字(三次握手已完成)。

7. 一个简单的值-结果例子

#include    "unp.h"
#include    <time.h>
int
main(int argc, char **argv)
{
    int                 listenfd, connfd;
    socklen_t           len;
    struct sockaddr_in  servaddr, cliaddr;
    char                buff[MAXLINE];
    time_t              ticks;
    //创建套接字
    listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    //初始化套接字
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;//IPv4协议
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//通配地址,一般为0
    servaddr.sin_port        = htons(13);//时间服务端口
    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    Listen(listenfd, LISTENQ);
    for ( ; ; ) {
        len = sizeof(cliaddr);
        connfd = Accept(listenfd, (SA *) &cliaddr, &len);
        printf("connection from %s, port %d\n",
            Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
            ntohs(cliaddr.sin_port));
        ticks = time(NULL);
        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
        Write(connfd, buff, strlen(buff));
        Close(connfd);
    }
}

编译执行上述代码,即可运行服务器端程序,那么我们开启客户端程序连接服务器,就会输出客户IP地址和端口号。

注意:此处如果出现bind error:Address already in use!,可参考:【unix网络编程第三版】ubuntu端口占用问题

# ./daytimes
connection from 127.0.0.1, port 53458
connection from 192.168.191.2, port 54358

8. fork和exec函数

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

#include<unistd.h>
pid_t fork(void);/* 在子进程中返回0,在父进程中为子进程ID,若出错则返回-1

任何子进程只有一个父进程,而且父进程通过getppid取得父进程ID,而父进程可以有许多子进程,而且无法获取各个子进程的进程ID,如果父进程想要知道子进程的ID,只能通过记录每次调用fork的返回值。

fork的两个典型用法:

(1) 一个进程创建一个自身的副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的某个操作。

(2) 一个进程想要执行另一个进程。fork函数创建一个副本,然后通过调用exec把其中一个副本替换成新的程序。

存放在硬盘你上的可执行程序文件能够被Unix执行的唯一方法:由一个现有的进程调用六个exec函数中的某一个。

#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(int fd,char *const argv[],char *const envp[]);

9. 并发服务器

像上面提到的时间获取服务器,属于迭代服务器。这种服务器都被一个单一客户占用,如果客户请求时间长,则该服务器被长期占用,这显然会影响效率。所以,我们希望服务器能尽可能同时服务多个用户。这种服务器称为并发服务器。

并发服务器利用fork函数创建一个子进程来服务客户。

int
main(int argc, char **argv)
{
    pid_t       pid;
    int     listenfd, connfd;
    socklen_t   len;
    struct sockaddr_in  servaddr;
    time_t      ticks;
    //创建套接字
    listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    //初始化套接字
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;//IPv4协议
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//通配地址,一般为0
    servaddr.sin_port        = htons(13);//时间服务端口
    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    Listen(listenfd, LISTENQ);
    for ( ; ; ) {
        connfd = Accept(listenfd, (SA *) &cliaddr, &len);
        if((pid = fork())==0)
        {
            close(listenfd);
            doit(connfd);
            close(connfd);
            exit();
        }
        Close(connfd);
    }
}

分析以上程序:

父进程:pid为子进程ID,不为0,则将connfd的引用套接字减1,父进程继续等待下一个客户连接

子进程:fork函数之后,监听套接字和已连接套接字的引用技术都加1,pid==0,首先监听套接字listenfd的引用计数减1(不会关闭监听套接字),然后执行客户所需的操作(doit),再关闭connfd(引用计数减1,此时为0)。子进程处理客户需求结束,exit关闭进程。

10 close函数

用来关闭套接字,并中止TCP连接。

#include <unistd.h>
int close(int sockfd);/* 若成功则返回0,出错则返回-1*/

close函数调用后只是将引用计数减1,只有当引用技术为0时,才会测地关闭该套接字,清理和资源释放。

11 getsockname和getpeername函数

getsockname函数返回与某个套接字关联的本地协议地址,getpeername函数返回与某个套接字关联的外地协议地址。

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

需要使用上述函数的情况如下:

(1) 在一个没有调用bind的TCP客户上,connect成功返回后,getsockname用于返回由内核赋予该连接的本地IP地址和本地端口号

(2) 在以端口0调用bind后,getsockname用于返回由内核赋予的本地端口号

(3) getsockname用于获取某个套接字的地址族

(4) 以通配IP地址调用bind的服务器上,与客户一旦建立连接,getsockname可用于返回由内核赋予该连接的本地IP地址

(5) 在一个服务器是由调用过accept的某个进程通过调用exec执行程序时,它只能通过getpeername来获取客户的IP和端口号

【unix网络编程第三版】阅读笔记(三):基本套接字编程的更多相关文章

  1. 鸟哥的LINUX私房菜基础篇第三版 阅读笔记 三 Linux磁盘与文件系统管理

    一.认识EXT2文件系统:     a.硬盘的组成:转动小马达+存储的磁盘+读写的机械臂     b.磁盘的一些概念              扇区为最小的物理储存单位,每个扇区为512B       ...

  2. 【Python网络编程】利用Python进行TCP、UDP套接字编程

    之前实现了Java版本的TCP和UDP套接字编程的例子,于是决定结合Python的学习做一个Python版本的套接字编程实验. 流程如下: 1.一台客户机从其标准输入(键盘)读入一行字符,并通过其套接 ...

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

    IPv4套接字地址结构 struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { uint8_t sin_len; /* length of ...

  4. C#高级编程第9版 阅读笔记(一)

    一.前言 C# 简洁.类型安全的面向对象的语言. .NET是一种在windows平台上编程的架构——一种API. C#是一种从头开始设计的用于.NET的语言,他可以利用.NET Framework及其 ...

  5. C++ primer 中文第三版 阅读笔记 第八章

    一.寄存器对象: 函数中频繁被使用的变量可以加上register就可声明为寄存器对象.对于寄存器对象,假如能够放到寄存器中就会放到寄存器中,放不到的话就放到内存中.比如 register int  a ...

  6. 鸟哥的LINUX私房菜基础篇第三版 阅读笔记 四 档案的文件系统的压缩和打包

    1.压缩文件案的用途与技术     a.用途,简单来说,就是节约磁盘空间.如果从传输角度讲,占用宽带也会小很多(Apache就有自动压缩的功能,节省宽带资源,提升网站的输出能力)     b.压缩技术 ...

  7. 鸟哥的LINUX私房菜基础篇第三版 阅读笔记 一

    1. Linux的档案权限与目录配置      一.基础知识:             a.分为三类,拥有者(owner).群组(group).其他人(other)             b.三个核 ...

  8. 鸟哥的LINUX私房菜基础篇第三版 阅读笔记 二

    Linux档案与目录管理 1.一些比较特殊的目录,需要用力的记下来 .         代表当前层目录 ..        代表上一层目录 -        代表前一个工作目录   (这个好屌!其他的 ...

  9. python cookbook第三版学习笔记三:列表以及字符串

    过滤序列元素: 有一个序列,想从其中过滤出想要的元素.最常用的办法就是列表过滤:比如下面的形式:这个表达式的意义是从1000个随机数中选出大于400的数据 test=[] for i in range ...

随机推荐

  1. 关于ajax的content-download时间过慢问题的解决方案与思考

    前言:   做前端架构很久很久了,经常到我这里都是些棘手的问题,之前没有养成很好的记录问题的习惯,以后会努力成文,积累. 于是今天就有个这篇文章.关于ajax的content-download时间过慢 ...

  2. 美团、java后台实习、面经

    3月27号投了美团java后台,29号收到面试邀请,好像是金融服务平台(提交简历的时候,我当时没注意随便填的···) 一面: 介绍项目经历 根据简历问一些问题:比如我简历上有区块链相关,会要求介绍一下 ...

  3. windows server 2003 远程桌面最大连接数调整与windows 2008远程桌面后,本地帐号自动锁定

    调整windows server 2003 最大远程连接数的步骤如下: 第1步.开始-->控制面板-->添加或删除程序-->添加/删除windows组件-->选择"终 ...

  4. 我的博客地址和github地址

    博客地址 http://www.cnblogs.com/sjzsjzsjz/ github地址  https://github.com/sjzsjzsjz

  5. java海量大文件数据处理方式

    1. 给定a.b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a.b文件共同的url? 方案1:可以估计每个文件安的大小为50G×64=320G,远远大于内存限制的4 ...

  6. python笔记九(迭代)

    一.迭代 通过for循环来遍历一个列表,我们称这种遍历的方式为迭代.只要是可迭代对象都可以进行迭代操作. 以下代码可以用来判断一个对象是否是可迭代的. 一类是集合数据类型,如list.tuple.di ...

  7. MySQL NULL 值处理

    MySQL NULL 值处理 我们已经知道MySQL使用 SQL SELECT 命令及 WHERE 子句来读取数据表中的数据,但是当提供的查询条件字段为 NULL 时,该命令可能就无法正常工作. 为了 ...

  8. MongoDB 自动增长

    MongoDB 没有像 SQL 一样有自动增长的功能, MongoDB 的 _id 是系统自动生成的12字节唯一标识. 但在某些情况下,我们可能需要实现 ObjectId 自动增长功能. 由于 Mon ...

  9. JavaScript中的事件模型

    JS中的事件 1.鼠标事件 onclick   ondbclick   onmouseover   onmouseout 2.HTML事件 onload   onunload   onsubmit   ...

  10. strut2接收参数的三种方式

    strut2接收参数有三种方式(普通属性\领域对象\模型驱动),分别对三种进行一个总结: 一.普通属性 Jsp代码 <body> <h1>普通属性</h1> < ...