4章 TCP套接字编程

概述

没有什么说的,就是一个小小系统调用,重点在于理解TCP协议栈。

套接字函数

/*
socket函数:指定进程期望的通信协议类型(包括协议族和套接字类型)。
__domain:协议族。
__type:指定套接字类型。
__protocol:某个协议类型,或为0选择前两个参数组合默认值。
返回套接字描述符sockfd。
*/
int socket (int __domain, int __type, int __protocol);

这里写图片描述
这里写图片描述
这里写图片描述

/*
connect函数:TCP客户与TCP服务器建立连接的函数。
TCP客户通过此函数建立与TCP服务器的连接,三次握手操作。
套接字含有客户机IP和端口号;
客户机不必一定bind绑定本地IP地址和端口,
和LwIP一样,内核会选择默认本地网卡IP以及临时端口。
__fd:socket函数返回的套接字描述符,描叙进程使用何种协议族。
__addr:地址结构指针,指定进程绑定服务器的地址和端口号。
__len:地址结构指针长度。
TCP套接字,connect发出三次握手,只有成功和出错才返回。
错误返回:
 1、SYN发送出去没有ACK信号,重复发送三次,还是无,则ETIMEDOUT。
 2、收到RST,表明服务器主机在指定端口没有进程运行,ECONNREFUSED。
 3、由于路由器收到ICMP错误,指示目的网络不可达到。
*/
int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);


/*
bind函数:将本地协议地址(地址和端口号)赋给套接字。
把绑定本地结构地址,对于TCP可以指定一个或者都指定或者全部都不指定。
对于端口:
    客户机可以不绑定端口,让内核临时选择端口。
    服务器必须绑定端口,因为都是用的众所周知的端口。
对于IP:
    IP地址必须是主机网络接口之一。
    对于客户机,就是为IP数据报源IP地址,可以不绑定,那么内核自动将外出网络接口来选择源IP。
    对于服务器就限定该套接字仅接收源IP地址为这个IP地址的客户连接。
*/
int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);

这里写图片描述

/*
listen函数:将套接字状态从close变成listen并且设定最大连接个数。
仅仅供TCP服务器使用。用队列处理多个连接请求。
n:表示内核应该为响应的套接字排队最大连接数目。内核为每个套接字维护两个队列:
1、未完成连接队列:每个这样的SYN分节对应其中一项,已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程,这些套接字处于SYN_RCVD状态。
2、已完成连接队列:完成之后便成为ESTABLISHED状态队列
*/
int listen (int __fd, int __n);

这里写图片描述

/*
TCP服务器调用,用于从已完成连接队列队头返回下一个己完成连接,如果已完成连接队列为空,那么进程被投入睡眠(假设套接字设置为阻塞方式)。
__fd:监听套接字。服务器仅仅创建一个监听套接字,在服务器生命期内一直存在。
__addr:返回客户协议地址。
__addr_len:传入结构长度,然后内核进行值-结果处理,传入为结构地址长度,返回为内核填入的信息。
返回:调用成功(也就是三次握手成功)将返回一个由内核生成的一个全新的已连接的套接字描述符,用于操作这个连接成功,通过此已连接套接字完成服务后,就被关闭。
如果对客户协议地址不感兴趣,后面两参数可以置空。
*/
int accept (int __fd, __SOCKADDR_ARG __addr,
           socklen_t *__restrict __addr_len);
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;//服务器IP地址为网卡IP地址
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//通用IP地址,网卡IP
    servaddr.sin_port        = htons(4098); /* daytime server */

    bind(listenfd, (SA *) &servaddr, sizeof(servaddr));//绑定本机IP地址和端口,设置结构体成员而已

    listen(listenfd, LISTENQ);//绑定之后就可以开始监听外部连接了,指定了排队最大客户连接数

    for ( ; ; ) {
        len = sizeof(cliaddr);//值传入
        connfd = accept(listenfd, (SA *) &cliaddr, &len );//进入accept投入睡眠,等待客户机被内核接受,握手完毕accept返回,len结果返回。
        //返回连接描述符,通过connfd和客户通信。
        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);//4次断开操作
    }
}

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SA  struct sockaddr
int main(int argc, char **argv)//客户机代码
{
    int                 sockfd, n;
    char                recvline[MAXLINE + 1];
    struct sockaddr_in  servaddr;

    if (argc != 2)
        err_quit("usage: a.out <IPaddress>");

    if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)//创建一个网际字节流套接字,TCP套接字
        err_sys("socket error");
    //客户机没有bind地址,直接填写服务器端地址和端口信息即可。内核自动加入源IP地址。
    bzero(&servaddr, sizeof(servaddr));//传递地址,将N字节清0
    servaddr.sin_family = AF_INET;
    servaddr.sin_port   = htons(4098);  /* daytime server */
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)//填入服务器IP地址和端口号13
        err_quit("inet_pton error for %s", argv[1]);

    if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)//TCP握手操作,交换数据
        err_sys("connect error");

    while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {//读取服务器应答消息
        recvline[n] = 0;    /* null terminate */
        if (fputs(recvline, stdout) == EOF)
            err_sys("fputs error");
    }
    if (n < 0)
        err_sys("read error");
    exit(0);
}

这里写图片描述

/*
close函数:关闭套接字并终止连接。默认行为是把该套接字标记成己关闭 ,然后立即返回到调用进程。该套接字描述符不能再由调用进程使用,也就是说它不能再作为readwrite的第一个参数。然后TCP将尝试发送己排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列。实际上close仅仅将套接字描述符引用计数减1并标记套接字为关闭,并没有激发四次断开操作,只有套接字引用计数为0时候,才会引发TCP四次断开序列。(当然这些都是TCP协议栈做的,不需要用户进程管)。shutdown可以直接将套接字引用计数清0,直接引发断开操作。



*/
int close(int sockfd);

并发服务器

int main(int argc, char **argv)//客户程序
{
    int                 listenfd, connfd;//监听fd和连接fd
    struct sockaddr_in  servaddr , cliaddr;
    socklen_t clilen;
    pid_t childpid;

    /*×××××××××××××××××××服务器端套路开始××××××××××××××××××××*/
    listenfd = Socket(AF_INET, SOCK_STREAM, 0);//监听套接字创建
    bzero(&servaddr, sizeof(servaddr));//清0
    servaddr.sin_family      = AF_INET;//服务器IP地址为网卡IP地址
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(4099);//服务器端必须设置对应端口号
    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));//绑定本机IP地址和端口,设置结构体成员而已
    Listen(listenfd, LISTENQ);//绑定之后,更改套接字为listen状态并指定了排队最大客户连接数
    /********************服务器端套路结束*********************/
    for ( ; ; ) {
        clilen = sizeof(cliaddr);
        connfd = Accept(listenfd , (SA *)&cliaddr , &clilen);//传入长度为了达到值-结果
        if((childpid = Fork()) == 0){
            Close(listenfd);//文件计数关闭一次
            server_echo(connfd);
            Close(connfd);
            exit(0);
        }//child,并行服务器就是不停fork处理
        Close(connfd);//等待下一个客户机连接。
    }
}

从accep返回,listenfd 和connfd 都被复制了,所以引用计数全部变为2。在客户端,第一步就是关闭其复制的listenfd,将其引用计数减去1,因为只需要一个进程服务器;在服务器端则关闭connfd,将其引用计数减去1,因为只需要新进程处理客户请求。状态变化过程如下图:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有时需要偏执狂

请我喝咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值