Linux下的TCP通信

2023-11-01

Linux下的TCP通信


今天我们介绍如何编写Linux下的TCP程序,关于UDP程序可以参考这里:

http://blog.csdn.net/htttw/article/details/7519971

本文绝大部分是参考《Linux程序设计(第4版)》的第15章套接字


服务器端的步骤如下:

1. socket: 建立一个socket

2. bind: 将这个socket绑定在某个文件上(AF_UNIX)或某个端口上(AF_INET),我们会分别介绍这两种。

3. listen: 开始监听

4. accept: 如果监听到客户端连接,则调用accept接收这个连接并同时新建一个socket来和客户进行通信

5. read/write:读取或发送数据到客户端

6. close: 通信完成后关闭socket



客户端的步骤如下:

1. socket: 建立一个socket

2. connect: 主动连接服务器端的某个文件(AF_UNIX)或某个端口(AF_INET)

3. read/write:如果服务器同意连接(accept),则读取或发送数据到服务器端

4. close: 通信完成后关闭socket




上面是整个流程,我们先给出一个例子,具体分析会在之后给出。例子实现的功能是客户端发送一个字符到服务器,服务器将这个字符+1后送回客户端,客户端再把它打印出来

Makefile:

[plain] view plain copy
  1. all:tcp_client.ctcp_server.c
  2. gcc-g-Wall-otcp_clienttcp_client.c
  3. gcc-g-Wall-otcp_servertcp_server.c
  4. clean:
  5. rm-rf*.otcp_clienttcp_server


tcp_server.c:

  1. #include<sys/types.h>
  2. #include<sys/socket.h>
  3. #include<sys/un.h>
  4. #include<unistd.h>
  5. #include<stdlib.h>
  6. #include<stdio.h>
  7. intmain()
  8. {
  9. /*deletethesocketfile*/
  10. unlink("server_socket");
  11. /*createasocket*/
  12. intserver_sockfd=socket(AF_UNIX,SOCK_STREAM,0);
  13. structsockaddr_unserver_addr;
  14. server_addr.sun_family=AF_UNIX;
  15. strcpy(server_addr.sun_path,"server_socket");
  16. /*bindwiththelocalfile*/
  17. bind(server_sockfd,(structsockaddr*)&server_addr,sizeof(server_addr));
  18. /*listen*/
  19. listen(server_sockfd,5);
  20. charch;
  21. intclient_sockfd;
  22. structsockaddr_unclient_addr;
  23. socklen_tlen=sizeof(client_addr);
  24. while(1)
  25. {
  26. printf("serverwaiting:\n");
  27. /*acceptaconnection*/
  28. client_sockfd=accept(server_sockfd,(structsockaddr*)&client_addr,&len);
  29. /*exchangedata*/
  30. read(client_sockfd,&ch,1);
  31. printf("getcharfromclient:%c\n",ch);
  32. ++ch;
  33. write(client_sockfd,&ch,1);
  34. /*closethesocket*/
  35. close(client_sockfd);
  36. }
  37. return0;
  38. }


tcp_client.c:

  1. #include<sys/types.h>
  2. #include<sys/socket.h>
  3. #include<sys/un.h>
  4. #include<unistd.h>
  5. #include<stdlib.h>
  6. #include<stdio.h>
  7. intmain()
  8. {
  9. /*createasocket*/
  10. intsockfd=socket(AF_UNIX,SOCK_STREAM,0);
  11. structsockaddr_unaddress;
  12. address.sun_family=AF_UNIX;
  13. strcpy(address.sun_path,"server_socket");
  14. /*connecttotheserver*/
  15. intresult=connect(sockfd,(structsockaddr*)&address,sizeof(address));
  16. if(result==-1)
  17. {
  18. perror("connectfailed:");
  19. exit(1);
  20. }
  21. /*exchangedata*/
  22. charch='A';
  23. write(sockfd,&ch,1);
  24. read(sockfd,&ch,1);
  25. printf("getcharfromserver:%c\n",ch);
  26. /*closethesocket*/
  27. close(sockfd);
  28. return0;
  29. }




如果我们首先运行tcp_client,会提示没有这个文件:


因为我们是以AF_UNIX方式进行通信的,这种方式是通过文件来将服务器和客户端连接起来的,因此我们应该先运行tcp_server,创建这个文件,默认情况下,这个文件会创建在当前目录下,并且第一个s表示它是一个socket文件



程序运行的结果如下图:




下面我们详细讲解:

1.

我们调用socket函数创建一个socket:

int socket(int domain, int type, int protocol)

domain:指定socket所属的域,常用的是AF_UNIX或AF_INET

AF_UNIX表示以文件方式创建socket,AF_INET表示以端口方式创建socket(我们会在后面详细讲解AF_INET

type:指定socket的类型,可以是SOCK_STREAM或SOCK_DGRAM

SOCK_STREAM表示创建一个有序的,可靠的,面向连接的socket,因此如果我们要使用TCP,就应该指定为SOCK_STREAM

SOCK_DGRAM表示创建一个不可靠的,无连接的socket,因此如果我们要使用UDP,就应该指定为SOCK_DGRAM

protocol:指定socket的协议类型,我们一般指定为0表示由第一第二两个参数自动选择。

socket()函数返回新创建的socket,出错则返回-1


2.

地址格式:

常用的有两种socket域:AF_UNIX或AF_INET,因此就有两种地址格式:sockaddr_un和sockaddr_in,分别定义如下:

  1. structsockaddr_un
  2. {
  3. sa_family_tsun_family;/*AF_UNIX*/
  4. charsun_path[];/*pathname*/
  5. }
  6. structsockaddr_in
  7. {
  8. shortintsin_family;/*AF_INET*/
  9. unsignedshortintsin_port;/*portnumber*/
  10. structin_addrsin_addr;/*internetaddress*/
  11. }

其中in_addr正是用来描述一个ip地址的:

  1. structin_addr
  2. {
  3. unsignedlongints_addr;
  4. }


从上面的定义我们可以看出,sun_path存放socket的本地文件名,sin_addr存放socket的ip地址,sin_port存放socket的端口号


3.

创建完一个socket后,我们需要使用bind将其绑定:

int bind(int socket, const struct sockaddr * address, size_t address_len)

如果我们使用AF_UNIX来创建socket,相应的地址格式是sockaddr_un,而如果我们使用AF_INET来创建socket,相应的地址格式是sockaddr_in,因此我们需要将其强制转换为sockaddr这一通用的地址格式类型,而sockaddr_un中的sun_family和sockaddr_in中的sin_family分别说明了它的地址格式类型,因此bind()函数就知道它的真实的地址格式。第三个参数address_len则指明了真实的地址格式的长度。

bind()函数正确返回0,出错返回-1



4.

接下来我们需要开始监听了:

int listen(int socket, int backlog)

backlog:等待连接的最大个数,如果超过了这个数值,则后续的请求连接将被拒绝

listen()函数正确返回0,出错返回-1



5.

接受连接:

int accept(int socket, struct sockaddr * address, size_t * address_len)

同样,第二个参数也是一个通用地址格式类型,这意味着我们需要进行强制类型转化

这里需要注意的是,address是一个传出参数,它保存着接受连接的客户端的地址,如果我们不需要,将address置为NULL即可。

address_len:我们期望的地址结构的长度,注意,这是一个传入和传出参数,传入时指定我们期望的地址结构的长度,如果多于这个值,则会被截断,而当accept()函数返回时,address_len会被设置为客户端连接的地址结构的实际长度。

另外如果没有客户端连接时,accept()函数会阻塞

accept()函数成功时返回新创建的socket描述符,出错时返回-1




6.

客户端通过connect()函数与服务器连接:

int connect(int socket, const struct sockaddr * address, size_t address_len)

对于第二个参数,我们同样需要强制类型转换

address_len指明了地址结构的长度

connect()函数成功时返回0,出错时返回-1



7.

双方都建立连接后,就可以使用常规的read/write函数来传递数据了



8.

通信完成后,我们需要关闭socket:

int close(int fd)

close是一个通用函数(和read,write一样),不仅可以关闭文件描述符,还可以关闭socket描述符





下面我们要介绍如何使用AF_INET了(这是更广泛使用的一种创建socket的方式):

同样还是先贴代码:

Makefile:

[plain] view plain copy
  1. all:tcp_client.ctcp_server.c
  2. gcc-g-Wall-otcp_clienttcp_client.c
  3. gcc-g-Wall-otcp_servertcp_server.c
  4. clean:
  5. rm-rf*.otcp_clienttcp_server


tcp_server.c:

  1. #include<sys/types.h>
  2. #include<sys/socket.h>
  3. #include<netinet/in.h>
  4. #include<arpa/inet.h>
  5. #include<unistd.h>
  6. #include<stdlib.h>
  7. #include<stdio.h>
  8. #definePORT9734
  9. intmain()
  10. {
  11. /*createasocket*/
  12. intserver_sockfd=socket(AF_INET,SOCK_STREAM,0);
  13. structsockaddr_inserver_addr;
  14. server_addr.sin_family=AF_INET;
  15. server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
  16. server_addr.sin_port=htons(PORT);
  17. /*bindwiththelocalfile*/
  18. bind(server_sockfd,(structsockaddr*)&server_addr,sizeof(server_addr));
  19. /*listen*/
  20. listen(server_sockfd,5);
  21. charch;
  22. intclient_sockfd;
  23. structsockaddr_inclient_addr;
  24. socklen_tlen=sizeof(client_addr);
  25. while(1)
  26. {
  27. printf("serverwaiting:\n");
  28. /*acceptaconnection*/
  29. client_sockfd=accept(server_sockfd,(structsockaddr*)&client_addr,&len);
  30. /*exchangedata*/
  31. read(client_sockfd,&ch,1);
  32. printf("getcharfromclient:%c\n",ch);
  33. ++ch;
  34. write(client_sockfd,&ch,1);
  35. /*closethesocket*/
  36. close(client_sockfd);
  37. }
  38. return0;
  39. }


tcp_client.c:

  1. #include<sys/types.h>
  2. #include<sys/socket.h>
  3. #include<netinet/in.h>
  4. #include<arpa/inet.h>
  5. #include<unistd.h>
  6. #include<stdlib.h>
  7. #include<stdio.h>
  8. #definePORT9734
  9. intmain()
  10. {
  11. /*createasocket*/
  12. intsockfd=socket(AF_INET,SOCK_STREAM,0);
  13. structsockaddr_inaddress;
  14. address.sin_family=AF_INET;
  15. address.sin_addr.s_addr=inet_addr("127.0.0.1");
  16. address.sin_port=htons(PORT);
  17. /*connecttotheserver*/
  18. intresult=connect(sockfd,(structsockaddr*)&address,sizeof(address));
  19. if(result==-1)
  20. {
  21. perror("connectfailed:");
  22. exit(1);
  23. }
  24. /*exchangedata*/
  25. charch='A';
  26. write(sockfd,&ch,1);
  27. read(sockfd,&ch,1);
  28. printf("getcharfromserver:%c\n",ch);
  29. /*closethesocket*/
  30. close(sockfd);
  31. return0;
  32. }


上面的程序我们使用了9734这一个任意选择的端口号进行通信,而ip地址则是本地的环回地址127.0.0.1


运行方法和前面一个例子完全一样,而程序的运行结果也和前面一个例子完全一样

虽然这样,我还是截张图好了:




主要的不同就是头文件去掉了<sys/un.h>,加入了<netinet/in.h>和<arpa/inet.h>


由于我们选择了AF_INET,因此需要使用sockaddr_in地址结构体,其中的sin_addr存放ip地址,sin_port存放端口号。

我们需要通过inet_addr()函数将ip地址字符串转换成内部的表示形式。另外由于intel机器使用little-endian顺序,和网络数据的big-endian顺序不一致,因此我们需要使用htons()函数进行转换(因为端口是16位,因此使用short)


unsigned long int htonl(unsigned long int hostlong)

unsigned short int htons(unsigned short int hostshort)

unsigned long int ntohl(unsigned long int netlong)

unsigned short int ntohs(unsigned short int netshort)


很好记,host to net, short就是htons,net to host, long就是ntohl


首先运行一下这个程序,然后输入netstat命令,就可以看到我们的程序啦(端口号9734)~~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux下的TCP通信 的相关文章

  • Day23-Model操作,Form操作和序列化操作

    1 搭建环境请参考 http www cnblogs com momo8238 p 7508677 html 二 Form操作 一般会创建forms py文件 单独存放form模块 Form 专门做数据验证 而且非常强大 有以下两个插件 1
  • nodejs通过request请求远程url的文件并下载到本地

    需要循环去下载远程文件 然后自己写了一个demo 可以直接运行 如下 文件下载var fs require fs var path require path var request require request 创建文件夹目录var di
  • cuda 9.1 linux 安装教程,Ubuntu 16.04安装NVIDIA的显卡驱动396和CUDA9.1

    Ubuntu安装NVIDIA的显卡驱动和CUDA Toolkit Ubuntu 这里是用16 04LTS桌面版 如果是17 04及以后版本 因为使用的显示服务器不同 可能又会有所不同 安装NVIDIA的显卡驱动经常出现启动后死循环进不去系统
  • linux——open函数

    1 使用open 函数调用 需要注意头文件
  • pbft为什么需要2f+1

    将阵营分为两拨 好节点阵营 坏节点阵营 现在坏节点阵营想要误导好节点 让其误以为已经有足够人数发出了投票 并且发生分歧 如何做 假设最极端情况 好节点被等分为两拨A B 坏节点加上任意一个阵营的人数都能达到足够人数达成投票 那坏节点就可以发
  • 法律逻辑学

    法律与逻辑的关系 1 法学离不开逻辑 逻辑是法学的基础 无论立法 司法 法治的实现 还是法学研究 法律教学都离不开逻辑 理性思维与非理性思维的最根本区别是思维是否具有逻辑性 思维逻辑性 就是思维活动遵循逻辑规律 正确地运用概念 恰当地做出判
  • linux echo

    linux echo功能说明 显示文字 语 法 echo ne 字符串 或 echo help version 补充说明 echo会将输入的字符串送往标准输出 输出的字符串间以空白字符隔开 并在最后加上换行号 参 数 n 不要在最后自动换行

随机推荐

  • 微信小程序自定义车牌输入组件(结合Weui)

    文章目录 概要 描述 效果 组件代码 引入 概要 我们都知道车牌是有一定规律的 本文实现了微信小程序中实现车牌输入功能 具有一定的参考价值 感兴趣的小伙伴们可以参考一下 描述 近期做了一个和车有关的小程序项目 有车肯定就有车牌 我们都知道车
  • 痞子衡嵌入式:盘点国内车规级MCU厂商

    大家好 我是痞子衡 是正经搞技术的痞子 今天痞子衡给大家介绍的是国内车规级MCU厂商及其产品 在汽车电子领域 MCU的应用非常广泛 大到车身控制与动力总成 小到雨刷车窗等控制单元 都离不开MCU的身影 但汽车领域产品必须要是高可靠 高安全的
  • L1-058 6翻了 (15 分)

    6翻了 思维错误原因以及正确思路的来源 确实是一趟循环遍历下来 但是对于 6 们的处理不太到位 单纯的用一次的if else 只是单纯的对字符串中每一个字符做处理 而我们这里需要统计连在一起的 6 们的个数 以及将他们整体替换掉 也就是说此
  • Dream------Hadoop--FSDataInputStream和FSDataOutputStream

    一 FSDataInputStream FileSystem中的open 方法实际上返回的是一个FSDataInputStream 而不是标准的java io类 这个类是java io DataInputStream 的一个子类 支持随机访
  • 1699 个词汇 的 计算机英语

    file n 文件 v 保存文件 command n 命令 指令 use v 使用 用途 program n 程序 line n 数据 程序 行 线路 display vt 显示 显示器 set v 设置 n 集合 key n 键 关键字
  • LayUi之树形结构的详解(附有全案例代码)

    Welcome Huihui s Code World 接下来看看由辉辉所写的关于LayUi的相关操作吧 目录 Welcome Huihui s Code World 一 什么是树形结构 二 树形结构在什么时候使用 常见使用场景 三 怎么完
  • mac 命令行 复制目录_从命令行复制目录

    mac 命令行 复制目录 Copying a directory for the sake of backup is something I do often especially when I m trying to figure out
  • 把token放入请求头

    1 jq 2 vue js 转载于 https www cnblogs com xiaobiaomei p 10917331 html
  • Python篇----Requests获取网页源码(爬虫基础)

    1 下载与安装 见其他教程 2 Requsts简介 Requests is an Apache2 Licensed HTTP library written inPython for human beings Python s standa
  • SQLServer查看各个表大小

    SQLServer查看各个表大小 SQLServer查看各个表大小 declare id int declare type character 2 declare pages int declare dbname sysname decla
  • Day83-面试前,先自己模拟下这 10 个问题

    不管是应届生还是社招生 相信你都经历过面试的摧残 面对咄咄逼人的面试官 面对随处可见的问题陷阱 我们应该如何面对 看完这边文章 相信你一定会有收获 Q1 请你先自我介绍下 回答技巧 回答尽量简短 控制在1 2分钟内 先简单的介绍一下自己 说
  • OAuth使用教程(一):初识OAuth

    一 OAuth介绍 OAuth 开放授权 是一个开放标准 允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息 而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容 OAuth允许用户提供一个令牌给第三方网站 一个
  • mmdetection3d 源码学习 mvxnet(多模态融合)

    mmdetection3d 源码学习 mvxnet 多模态融合 配置文件 dv mvx fpn second secfpn adamw 2x8 80e kitti 3d 3class py 模型 model settings voxel s
  • 餐饮便利店服装店线下零售场景都有刷脸支付需求

    此前 支付宝已经开始打通江浙沪数个城市之间的二维码乘车 前几天也在北欧促成了两大移动支付APP的互通 如今刷脸支付还未普及 但是刷脸支付的前景可谓一片大好 未来包括超市 便利店 药房 服装店等等的众多线下零售场景都会有刷脸支付的需求 常见蜻
  • MySQL之Innodb锁机制:Next-Key Lock 浅谈

    阅读 InnoDB存储引擎 第六章涉及锁的三种算法 整理结合转载文档 https www cnblogs com zhoujinyi p 3435982 html 数据库使用锁是为了支持更好的并发 提供数据的完整性和一致性 InnoDB是一
  • 6个步骤,告诉你如何用树莓派和机器学习DIY一个车牌识别器!(附详细分析)...

    作者 Robert Lucian Chiriac 翻译 天道酬勤 编辑 Carol 出品 AI科技大本营 ID rgznai100 几个月前 作者开始考虑让汽车能够具备检测和识别物体的能力 他很喜欢这个主意 因为已经见识到了特斯拉的能力 并
  • (渗透学习)理解java反序列化漏洞原理---层序渐进

    没真正学过java 对很多概念理解的不清晰 所以下面所有都是参考资料结合我自己的理解 可能存在错误 1 为什么要序列化 因为只有字节数据才能进行存储和传输 所以为了使对象 如class类 能够存储 传输 就需要将对象转成字节的形式 存储 把
  • tomcat显示启动成功,访问不了

    问题阐述 解压tomcat成功之后 发现访问不了 问题分析 1 开通了防火墙 但是没有放开8080端口号 查看防火墙状态 firewall cmd state 显示running表示防火墙是开启状态 执行放开8080端口的命令 firewa
  • 三个月后,快手To B怎么样了?

    未来 如何独立作战和走出快手的TOC 客户资源圈 或将成为快手TOB新的十字路口 作者 斗斗 编辑 皮爷 出品 产业家 人口红利终结 流量红利终结 超常规的高速增长终结 TOC模式的路越来越难走了 快手与抖音作为短视频行业 的两大巨头 主要
  • Linux下的TCP通信

    Linux下的TCP通信 今天我们介绍如何编写Linux下的TCP程序 关于UDP程序可以参考这里 http blog csdn net htttw article details 7519971 本文绝大部分是参考 Linux程序设计 第