TCP服务器客户端编程流程

  • TCP编程流程
  • 主机字节序列和网络字节序列
  • 套接字地址结构
    • 通用socket地址结构
    • 一般使用会定义一个专用的套接字结构
  • IP地址转换函数
  • 网络编程接口
  • TCP服务段代码实现
  • TCP客户端代码实现
  • TCP服务端客户端代码详解
  • TCP流式服务和粘包问题
  • netstat命令

TCP编程流程

TCP提供的是面向连接、可靠的、字节流服务

客户端与服务端分别在两个不同的主机上,TCP连接流程固定

首先第一步是创建套接字socket()(客户端创建套接字一般不需要指定端口号),相当于区分不同的连接,然后绑定bind()来指定ip与端口,然后创建监听队列listen(),等待接收连接accept()会阻塞在这一步等待链接,这个时候客户端执行connect()建立链接然后会进行三次握手进行连接,随后客户端与服务端就可以发送接收数据了,close()关闭套接字

主机字节序列和网络字节序列

主机字节序列分为大端字节序和小端字节序,不同的主机采用的字节序列可能不同。大端字节序是指一个整数的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。 在两台使用不同字节序的主机之间传递数据时,可能会出现冲突。所以,在将数据发送到网络时规定整形数据使用大端字节序,所以也把大端字节序成为网络字节序列。对方接收到数据后,可以根据自己的字节序进行转换。

不同主机对字节的存储顺序可能不一样,存在小段字节序和大段字节序

Linux系统提供了如下4个函数来完成主机字节序和网络字节序之间的转换:

较常用的:uint16_t htons(uint16_t hostshort) 主机序列转网络序列,十六位较多

套接字地址结构

通用socket地址结构

socket网络编程接口中表示socket地址的是结构体sockaddr(对链接到唯一表示),其定义如下:

这是通用套接字结构

一般使用会定义一个专用的套接字结构

TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用 socket 地址结构体,它们分
别用于 IPV4 和 IPV6:


IP地址转换函数

通常,人们习惯用点分十进制字符串表示 IPV4 地址,但编程中我们需要先把它们转化为整数方能使用,下面函数可用于点分十进制字符串表示的 IPV4 地址和网络字节序整数表示的 IPV4 地址之间的转换:

将字符串,转为32位整型

网络编程接口

  • int socket(int domain, int type, int protocol);
    创建套接字,成功返回套接字文件描述符;参数:协议族,服务类型,最后一般设0,表示默认协议
  • int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    绑定套接字,成功返回0;参数:套接字描述符,地址结构,socket地址长度
  • int listen(int sockfd, int backlog);
    创建一个监听队列以存储待处理的客户连接,成功返回0;参数:被监听的socket套接字,处于完全连接状态的socket上限
  • int accept(int sockfd, struct sockaddr *addr,socklen_t *addrlen);
    accept()从listen监听队列中接收一个连接,成功返回一个新的连接socket,该socket唯一标识了被接受的这个连接;参数:监听socket,获取被接受连接的远端socket地址,指定socket地址长度
  • int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
    connect()客户端需要通过此系统调用来主动与服务器建立连接,成功返回0;参数:socket()返回的参数,服务器监听的socket地址,地址长度
  • int close(int sockfd);
    close()关闭一个连接,就是关闭该连接对应的socket套接字
  • ssize_t recv(int sockfd, void *buff, size_t len, int flags);
  • ssize_t send(int sockfd, const void *buff, size_t len, int flags);
    recv()读取套接字是的数据,buff与len参数分别指顶读取缓冲区的位置和大小
    send()发送套接字是的数据,buff与len参数分别指顶写缓冲区的位置和数据长

TCP服务段代码实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{//创建套接字//参数://AF_INET 指定协议族为ipv4//SOCK_STREAM 服务类型 使用流式套接字//一般为0int sockfd = socket(AF_INET,SOCK_STREAM,0);assert(sockfd != -1);//创建套接字地址结构struct sockaddr_in saddr,caddr;//caddr存放客户端的ip与端口memset(&saddr,0,sizeof(saddr));//清空saddrsaddr.sin_family = AF_INET;//协议族或者叫地址族saddr.sin_port = htons(6000);//端口 转换主机序列到网络序列saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//ip 字符串转换为整形,127.0.0.1是用于本地测试的ip地址//绑定套接字//参数://套接字描述符//地址结构,需要传入通用套接字结构所有进行强转//地址结构大小int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));assert(res != -1);//创建监听队列//参数://套接字描述符//监听队列长度listen(sockfd,5);while(1){//循环 接受客户端的连接//参数://套接字描述符//存放客户端的地址结构//地址结构大小int len = sizeof(saddr);int c = accept(sockfd,(struct sockaddr*)&caddr,&len);if(c < 0){continue;}printf("accept c = %d\n",c);char buff[128] = {0};recv(c,buff,127,0);//从c 也就是客户端接受数据,存放在buff,大小为127,防止将其占满,标志位给0//也可以通过read操作,因为c也是文件描述符//实际上是与c进行沟通,而sockfd只作用于循环前printf("buff = %s\n",buff);send(c,"ok",2,0);//发送给c客户端,发送OK,大小为2,标志位0//也可以用write(),与上同理close(c);//关闭c}
}

TCP客户端代码实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{//socket() connect() send() recv() close()int sockfd = socket(AF_INET,SOCK_STREAM,0);assert(sockfd != -1);struct sockaddr_in saddr;//客户端(caddr)自己的端口ip由系统分配memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));assert(res != -1);printf("input:\n");char buff[128] = {0};fgets(buff,127,stdin);send(sockfd,buff,strlen(buff),0);//write()也可以做到memset(buff,0,128);//清空为了接受 来自服务渠道数据recv(sockfd,buff,127,0);//read()也可以做到printf("read:%s\n",buff);close(sockfd);}

TCP服务端客户端代码详解

我们将服务器与客户端代码都运行起来查看


服务端在这里是死循环一直执行的,只能手动暂停

当我们运行了服务端之后,6000号端口就会被我们占用,我们输入 netstat -natp 指令查看

我们看到 “127.0.0.1:6000” 的信息,当我们再次执行服务端代码,就会失败,因为端口已经被占用了

只有端口被释放出来,才能再次执行


这里的消息队列长度,可以理解为有座位,座位的数目就是我们的长度,当我们listen后会创建两个队列,一个是未完成三次握手的,一个是完成三次握手的;当我们客户端发起链接后(connect)就会将链接放在上面未完成三次握手的队列中,等完成三次握手就放入下面的队列中,而accept就相当于从完成三次握手的队列中拿到链接进行处理得到一个新的c,就可以通过c进行交互,完成三次握手的队列长度就是我们设置的5,这里之所以设置长度,就是为了防止等待造成的问题,并且这里的5并不代表服务端只能处理5个客户端

listen并不会阻塞,而accept会阻塞,例如我们在此处代码加一行打印,我们编译运行服务器后会发现,服务器代码会阻塞在这一步

等待客户端的连接,这时候运行客户段代码

就会继续运行,阻塞在recv处等待接收数据,而客户端阻塞在fgets处,客户端输入数据后服务端继续运行

这时候客户端运行结束,服务器继续阻塞等待新的客户段连接,我们再次运行客户端代码,服务端就会接着下一步

服务段的ip与端口,在写客户端代码的时候需要自己添加进去,而客户端的ip与端口则是由系统自动生成的,其他更多的在代码注释中介绍

TCP流式服务和粘包问题


当我们通过TCP发送数据,并不是直接发送给对方,而是先将数据存放在发送缓冲区中,形成字节流;然后按照底层协议将数据重新打包再发送给对方,对方得到打包的数据,再将其放在接收TCP缓冲区,再接收到的数据就是一整个结合

这样看就会出现我们说的粘包问题(多次发送的数据会被对方一次收到),粘包问题有时候会有影响有时候没有影响,比如我们传输一个文件,至于数据发送几次接受几次并不影响文件传输的目的,而假如我们去发送长方体的长宽高,需要接收方返回体积,这时候我们send()发送了三次,发生粘包问题,就会只收到一次就不能区分长宽高
我们可以通过一个方法来解决这类问题,再send()之后,加一个recv()来接收对方接收成功发来的讯息,这样就可以将不同的数据进行错开;或者可以通过将发送数据按某种格式进行分割(【3】【4】【5】,例如这样通过中括号进行分割)

我们在执行send()的时候只能说明数据放在了发送缓冲区中,但是是否发送给了对方的接收缓冲区我们并不知道

netstat命令


我们将原先服务端代码中的循环部分进行修改如下:

while(1){int len = sizeof(saddr);printf("accept wait ......\n");int c = accept(sockfd,(struct sockaddr*)&caddr,&len);if(c < 0){continue;}printf("accept c = %d\n",c);while(1){char buff[128] = {0};int n = recv(c,buff,127,0);if(n<=0){break;}printf("buff = %s\n",buff);send(c,"ok",2,0);}close(c);}

原本只进行一次接收并打印,随后就会关闭链接,现在我们将其循环起来,只要对方不关闭链接(recv返回值为0)就能不断接收数据

我们将原本的客户端代码也进行修改

while(1){printf("input:\n");char buff[128] = {0};fgets(buff,127,stdin);if(strncmp(buff,"end",3) == 0){break;}send(sockfd,buff,strlen(buff),0);//write()也可以做到memset(buff,0,128);//清空为了接受 来自服务渠道数据recv(sockfd,buff,127,0);//read()也可以做到printf("read:%s\n",buff);}close(sockfd);

将原本只发送并接受一次数据就关闭链接,修改为只要不从键盘获取end,就不断发送获取数据

我们将代码编译运行起来

我们再次修改服务端的代码,我们将此处本来只能获得127字节大小改为1,每次接收1字节并打印

我们再次运行代码查看

有些主机可能会出现,直接受到一个ok的返回
我们使用netstat -natp来查看

可以看到客户端与服务端的端口与ip,再去观察我们的接收缓冲区,假如向下下图那样缓冲区并不为空,说明还有数据没有被recv接收(Recv-Q 接收缓冲区 Send-Q 发送缓冲区)

TCP服务器客户端编程流程相关推荐

  1. UDP服务器客户端编程流程

    UDP服务器客户端编程流程 UDP编程流程 UDP服务端代码实现 UDP客户端代码实现 UDP服务端客户端代码详解 UDP编程流程 UDP提供的是无连接.不可靠的.数据报服务 UDP是尽最大能力进行传 ...

  2. Qt的Tcp服务器多线程编程-附带代码展示

    Qt的Tcp服务器多线程编程-附带代码展示 该程序主要实现tcp服务器如何使用多线程的方式来连接多个客户端,此文章没有实现客户端的多线程编程. 创建子线程时需要注意的点: 1.子线程与主线程之间交互数 ...

  3. TCP 服务器/客户端(实现下载)

    TCP/IP :       TCP/IP:在网络通信中,TCP/IP是主流协议()       应用层:用户自定义的协议(HTTP,EMAIL,),用于用户之间数据的传送       传输层:(传输 ...

  4. TCP服务器/客户端实例(C/C )

    1.1.Linux下的TCP服务器: #include <stdio.h> #include <stdlib.h> #include <string.h> #inc ...

  5. Python网络编程之TCP服务器客户端(二)

    传输控制协议(官方术语为TCP/IP协议)是互联网的重要组成部分.TCP的第一个版本是在1974年定义的,它建立在网际层协议(IP)提供的数据包传输技术之上.TCP使得应用程序可以使用连续的数据流进行 ...

  6. Java TCP/UDP socket 编程流程总结

    最近正好学习了一点用java socket编程的东西.感觉整体的流程虽然不是很繁琐,但是也值得好好总结一下. Socket Socket可以说是一种针对网络的抽象,应用通过它可以来针对网络读写数据.就 ...

  7. 【Chat】实验 -- 实现 C/C++下TCP, 服务器/客户端 多人聊天室

    本次实验利用TCP/IP, 语言环境为 C/C++ 利用套接字Socket编程,以及线程处理, 实现Server/CLient 之间多人的聊天系统的基本功能. 结果大致如: 下面贴上代码(参考参考.. ...

  8. Linux 高级I/O之poll函数及简单服务器客户端编程

    当需要同时监听多个文件描述符时,就需要I/O复用函数,I/O复用函数有select.poll.epoll,今天主要使用poll函数.  poll()接受一个指向结构'struct pollfd'列表的 ...

  9. tcp服务器客户端状态图

    转载于:https://www.cnblogs.com/rspb/p/4735899.html

最新文章

  1. 如何使用工具包 (NLTK) 开发NLP 项目?(附教程)
  2. python退出函数_python 退出程序的方式
  3. boost::mp11::mp_plus相关用法的测试程序
  4. android真机单元测试,Android 单元测试入门
  5. 我的gentoo安装纪念贴移植空间版
  6. 2016年大数据Spark“蘑菇云”行动之spark streaming消费flume采集的kafka数据Directf方式...
  7. Spring生态系统(Spring可能大家都在用,很少去关注整体架构)
  8. Web应用程序开发课程总结
  9. CS224n(一) 自然语言处理与深度学习简介
  10. 计算机用word做贺卡,运用Word制作电子贺卡教学设计
  11. 干货丨让你更容易影响别人的 52 个小技巧
  12. python培训骗局
  13. abaqus python 读取文件_ABAQUS Command 如何调用或执行 Python 脚本文件
  14. 【leetcode】838. 推多米诺(模拟)
  15. ANSYS经典界面保存单元解和节点解
  16. 基于MATLAB下巴特沃斯IIR数字滤波器的实现
  17. zara小程序怎么付款_能扫码付款并且可以打折的微信小程序怎么做?
  18. burpsuite安装的问题
  19. MatLab中矢量图的导出
  20. 疫情当下无法继续学画画?来美术集线上画室在家好好学美术~

热门文章

  1. OSChina 中秋节乱弹 ——加班比抢了我的小鱼干,更让我难过!
  2. 选择收货地址,省市区街道联动
  3. WTN6040-8S语音播报芯片在抽油烟机上的应用- 提升厨房智能化体验
  4. 基于JSP的足球直播论坛
  5. 观点 | NFT 狂热与 ETH 的价值捕获
  6. 大服务器实装维护公告,大服务器实装启动 首批配对服务器出炉
  7. 【Linux】CentOS 7安装 MySQL
  8. Python中的多进程并行简明教程
  9. ICRA 2022 优秀论文
  10. day16-正则表达式和面向对象