网络编程之TCP编程 - 实践

基于  C/S :客户端(client)/服务器端(server)

1.流程

       

2.  函数接口

所有函数所需头文件:

#include  #include

系统定义好了用来存储网络信息的结构体

ipv4通信使用的结构体:struct sockaddr_in

我们只需要直接定义结构体变量即可

2.1 创建套接字socket()

int socket(int domain, int type, int protocol);功能:创建套接字参数:    domain:协议族     AF_UNIX, AF_LOCAL  本地通信     AF_INET            ipv4     AF_INET6            ipv6   type:套接字类型     SOCK_STREAM:流式套接字     SOCK_DGRAM:数据报套接字      SOCK_RAW:原始套接字   protocol:协议  一般填0 自动匹配底层      根据type系统默认自动帮助匹配对应协议     传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP     网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)   返回值:    成功 文件描述符-- > sockfd (用于连接)    失败 -1,更新errno

 注意:TCP服务器端有两类文件描述符 !!!
        一类用于连接的文件描述符(sockfd-->socket函数返回值) 只有一个
        一类用于通信的文件描述符(acceptfd-->accept函数返回值) 可以多个

2.2绑定套接字bind()

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);功能:绑定参数:    socket:套接字    addr:用于通信结构体 (提供的是通用结构体,需要根据选择通信方式,填充对应结构体-通信当时socket第一个参数确定)       addrlen:结构体大小   返回值:成功 0   失败-1,更新errno

由于系统定义好的记录网络信息的结构体是struct sockaddr_in类型,因此,bind第二个参数使用时结构体变量地址的时候要强制类型转换

2.3监听listen()

int listen(int sockfd, int backlog);功能:监听,将主动套接字变为被动套接字参数: sockfd:套接字 backlog:(目前已无具体作用,写个正数即可)    同时响应客户端请求链接的最大个数,不能写0.    不同平台可同时链接的数不同,一般写6-8个    (队列1:保存正在连接)    (队列2,连接上的客户端) 返回值:成功 0   失败-1,更新errno

注意:listen作用:主动套接字变为被动套接字!!!

2.4接收客户端连接请求 accept()

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);accept(sockfd,NULL,NULL);功能:阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,则accept()函数返回,返回一个用于通信的套接字文件描述符;参数:   Sockfd :套接字   addr: 链接客户端的ip和端口号      如果不需要关心具体是哪一个客户端,那么可以填NULL;   addrlen:结构体的大小     如果不需要关心具体是哪一个客户端,那么可以填NULL;返回值:      成功:文件描述符; //用于通信		失败:-1,更新errno

2.5接受消息recv()

ssize_t recv(int sockfd, void *buf, size_t len, int flags);功能: 接收数据 参数:     sockfd: acceptfd ;    buf  存放位置    len  大小    flags  一般填0,相当于read()函数    MSG_DONTWAIT  非阻塞返回值:    0   成功接收的字节个数

2.6发送消息send()

ssize_t send(int sockfd, const void *buf, size_t len, int flags);功能:发送数据参数:    sockfd:socket函数的返回值    buf:发送内容存放的地址    len:发送内存的长度    flags:如果填0,相当于write();

2.7连接服务器connect()

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);功能:用于连接服务器;参数:     sockfd:socket函数的返回值     addr:填充的结构体是服务器端的;     addrlen:结构体的大小返回值       -1 失败,更新errno      正确 0

2.8 关闭套接字 close()

即关闭套接字文件

close(文件描述符);

3.服务器端

 按照流程:

(1)创建流式套接字socket()

     

(2)指定网络信息

 (3)绑定套接字bind()

(4) 监听listen()

(5) 等待客户连接信息accept()

 注意:

在服务器端使用客户的网络信息时:

(6)收发消息 send() recv()

(7) 关闭套接字

源代码:

#include #include #include #include #include #include #include #include #include  //  $$ 服务器端 $$   int main(int argc, char const *argv[]){    /*创建流式套接字*/    int sockfd = socket(AF_INET, SOCK_STREAM, 0);    if (sockfd AF_INET)、IP地址、端口号等*/     // 服务器的网络信息通过一个系统定义好的结构体来描述    struct sockaddr_in saddr;                     // 定义一个结构体变量    saddr.sin_family = AF_INET;                   // 确定协议族-->IPv4    saddr.sin_port = htons(atoi(argv[1]));        // 确定使用的端口号    saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 确定服务器IP地址     /*绑定套接字*/     int t1 = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));    if (t1 相当于read(acceptfd,buf,128)            if (ret < 0)            {                perror("recv err");                return -1;            }            else if (ret == 0)            {                printf("客户退出\n");                break;            }            else            {                printf("%s 接收成功\n", buf);                memset(buf, 0, sizeof(buf));            }        }        close(acceptfd);    }    /* 关闭套接字 */     close(sockfd);     return 0;}

4.客户端

按照流程:

(1)创建流式套接字socket()

(2)指定服务器网络信息

(3)连接服务器connect()

(4)发送接受消息 send()  recv()

(5)关闭套接字

源代码:

#include #include #include #include #include #include #include #include #include //  $$ 客户端 $$   int main(int argc, char const *argv[]){    /*创建流式套接字*/    int sockfd = socket(AF_INET, SOCK_STREAM, 0);    if (sockfd 相当于write(sockfd,buf,sizeof(buf))    }     /* 关闭套接字 */    close(sockfd);     return 0;}

5.TCP粘包问题

tcp粘包

tcp拆包

6.三次握手四次挥手

三次握手建立连接

第一次握手:客户通过调用connect进行主动打开(active open)。这引起客户TCP发送一个SYN(表示同步)分节(SYN=J),它告诉服务器客户将在连接中发送数据的初始序列号。并进入SYN_SEND状态,等待服务器的确认。

第二次握手:服务器必须确认客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器以单个字节向客户发送SYN和对客户SYN的ACK(表示确认),此时服务器进入SYN_RECV状态。

第三次握手:客户收到服务器的SYN+ACK。向服务器发送确认分节,此分节发送完毕,客户服务器进入ESTABLISHED状态,完成三次握手。

面试题

  • 描述一下三次握手
  • tcp连接过程三次握手
  • tcp连接的过程中有哪些状态切换?
  • tcp三次握手发生在两个函数之前?connectaccept
  • 为什么一定是三次握手,不能是两次握手?

主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而导致不必要的错误和资源的浪费。

两次握手只能保证单向连接是畅通的。因为TCP是一个双向传输协议,只有经过第三次握手,才能确保双向都可以接收到对方的发送的数据。

四次挥手释放连接

第一次挥手:某个应用进程首先调用close,我们称这一端执行主动关闭。这一端的TCP于是发送一个FIN分节,表示数据发送完毕。

第二次挥手:接收到FIN的另一端执行被动关闭(passive close)。这个FIN由TCP确认。它的接收也作为文件结束符传递给接收端应用进程(放在已排队等候应用进程接收到任何其他数据之后)

第三次挥手:一段时间后,接收到文件结束符的应用进程将调用close关闭它的套接口。这导致它的TCP也发送一个FIN。

第四次挥手:接收到这个FIN的原发送端TCP对它进行确认。

面试题

  • 描述四次挥手
  • 第二次挥手与第三次挥手之间有一段时间间隔是为什么?

用于被动关闭方进行剩余数据的数据传输

  • 第四次挥手之后主动断开方会等待一段时间再关闭,这个等待的时间是多少?为什么要等待?

等待时间为2MSL,1MSL是报文在系统内的最大存活时间,等待2MSL的时间是为了确保ACK包成功到达被动断开方

在第一个MSL时间内,是被动断开方等待主动断开方ACK报文,若没有在1msl的时间内收到,会超时重传FIN报文,主动断开方在剩余的1MSL时间,接收到被动断开方发送的新的FIN

posted @ 2025-07-18 12:09  yjbjingcha  阅读(11)  评论(0)    收藏  举报