套接字通信的客户端和服务端的实现

news/2025/1/14 22:12:51/

知识了解:

1.IP:本质是一个整型数,用于表示计算机在网络中的地址。IP协议版本有两个:IPv4和IPv6

IPv4(Internet Protocol version4):

  1. 使用一个32位的整型数描述一个IP地址,4个字节,int型
  2. 也可以使用一个点分十进制字符串描述这个IP地址: 192.168.247.135
  3. 分成了4份,每份1字节,8bit(char),最大值为255
    1. 0.0.0.0 是最小的IP地址
    2. 255.255.255.255是最大的IP地址
  4. 按照IPv4协议计算,可以使用的IP地址共有2^32个

IPv6(Internet Protocol version6):

  1. 使用一个128位的整型数描述一个IP地址,16个字节
  2. 也可以使用一个字符串描述这个IP地址: 2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b
  3. 分成了8份,每份2字节,每一部分以16进制的方式表示
  4. 按照IPv6协议计算,可以使用的IP地址共有2^18个数

2.端口

端口的作用是定位到主机上的某一个进程,通过这个端口进程就可以接收到对应的数据了。

比如:在电脑上运行了微信和QQ,小明通过客户端给我的微信发消息,电脑上的微信就收到了消息,为什么?

  1. 运行在电脑上的微信和QQ都绑定了不同的端口
  2. 通过IP地址可以定位到某一台主机,通过端口就可以定位到主机上的某一个进程
  3. 通过指定的IP和端口,发送数据的时候对端就能接收到数据了

端口也是一个整形数,unsigned short,一个16位整型数,有效端口的取值范围是:0~65535(0~2^16-1)

提问:计算机中所有的进程都需要关联一个端口吗?一个端口可以被重复使用吗?

  1. 不需要,如果这个进程不需要网络通信,那么这个进程就不需要绑定端口的
  2. 一个端口只能给某一个进程使用,多个进程不能同时使用同一个端口

3. 字节序转换函数

当格式化的数据在两台使用不同字节序的主机之间直接传递时,接收端必然错误的解释之。解决问题的方法是:发送端总是把要发送的数据转换成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。

网络字节顺序是 TCP/IP 中规定好的一种数据表示格式,它与具体的 CPU 类型、操作系统等无关,从而 可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序采用大端排序方式。

BSD Socket提供了封装好的转换接口,方便程序员使用。包括从主机字节序到网络字节序的转换函数: htons、htonl;从网络字节序到主机字节序的转换函数:ntohs、ntohl。

h - host 主机,主机字节序
to - 转换成什么
n - network 网络字节序
s - short unsigned short
l - long unsigned int#include <arpa/inet.h>
// 转换端口
uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序
uint16_t ntohs(uint16_t netshort); // 网络字节序 - 主机字节序
// 转IP
uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序
uint32_t ntohl(uint32_t netlong); //  网络字节序 - 主机字节序

4.socket 地址

socket地址其实是一个结构体,封装端口号和IP等信息。后面的socket相关的api中需要使用到这个socket地址。客户端 -> 服务器(IP, Port)

5. IP地址转换(字符串ip-整数 ,主机、网络 字节序的转换)

#include <arpa/inet.h>
// p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst);af:地址族: AF_INET AF_INET6src:需要转换的点分十进制的IP字符串dst:转换后的结果保存在这个里面// 将网络字节序的整数,转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);af:地址族: AF_INET AF_INET6src: 要转换的ip的整数的地址dst: 转换成IP地址字符串保存的地方size:第三个参数的大小(数组的大小)返回值:返回转换后的数据的地址(字符串),和 dst 是一样的

6. TCP通信流程

// TCP 通信的流程
// 服务器端 (被动接受连接的角色)
1. 创建一个用于监听的套接字- 监听:监听有客户端的连接- 套接字:这个套接字其实就是一个文件描述符
2. 将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)- 客户端连接服务器的时候使用的就是这个IP和端口
3. 设置监听,监听的fd开始工作
4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字
(fd)
5. 通信- 接收数据- 发送数据
6. 通信结束,断开连接// 客户端
1. 创建一个用于通信的套接字(fd)
2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信- 接收数据- 发送数据
4. 通信结束,断开连接

7.套接字通信的服务器端实现

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>int main() {// 1.创建监听的套接字int fd = socket(AF_INET,SOCK_STREAM,0);if(fd == -1) {perror("socket");return -1;}// 2.绑定本地的IP portstruct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_addr.s_addr = INADDR_ANY; // 0 = 0.0.0.0 对于0来说,大端和小端是没有区别的的,因此不需要转换saddr.sin_port = htons(9999);//主机字节序转换成网络字节序int ret = bind(fd,(struct sockaddr*)&saddr,sizeof(saddr));if(ret == -1) {perror("bind");return -1;}// 3.设置监听ret = listen(fd,128);if(ret == -1) {perror("listen");return -1;}// 4.阻塞并等待客户端的连接struct sockaddr_in caddr;int addrlen = sizeof(caddr);int cfd = accept(fd,(struct sockaddr*)&caddr,&addrlen);if(cfd == -1) {perror("accept");return -1;}// 连接建立成功,打印客户端的IP和端口信息char ip[32];printf("客户端的IP: %s,端口: %d\n",inet_ntop(AF_INET,&caddr.sin_addr.s_addr,ip,sizeof(ip)),ntohs(caddr.sin_port));// 5.通信while(1) {// 接收数据char buff[1024];int len = recv(cfd,buff,sizeof(buff),0);if(len > 0) {printf("client say: %s\n",buff);send(cfd,buff,len,0);}else if(len == 0) {printf("客户端已经断开了连接...\n");break;}else{perror("recv");break;}}// 关掉文件描述符close(fd);close(cfd);return 0;
}

8.套接字通信的客户端实现

client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>int main() {// 1.创建套接字int fd = socket(AF_INET,SOCK_STREAM,0);if(fd == -1) {perror("socket");return -1;}// 2.连接服务器IP portstruct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(9999);inet_pton(AF_INET,"192.168.88.129",&saddr.sin_addr.s_addr);int ret = connect(fd,(struct sockaddr*)&saddr,sizeof(saddr));if(ret == -1) {perror("connect");return -1;}int number = 0;// 3.通信while(1) {// 发送数据char buff[1024];sprintf(buff,"你好,呵呵哒,%d...\n",number++);send(fd,buff,strlen(buff) + 1,0);//接收数据memset(buff,0,sizeof(buff));int len = recv(fd,buff,sizeof(buff),0);if(len > 0) {printf("server say: %s\n",buff);}else if(len == 0) {printf("服务器已经断开了连接...\n");break;}else{perror("recv");}sleep(1);}// 关闭文件描述符close(fd);return 0;
}


http://www.ppmy.cn/news/1001837.html

相关文章

华为OD机试真题 Java 实现【寻找最大价值的矿堆】【2023 B卷 100分】,附详细解题思路

目录 专栏导读一、题目描述二、输入描述三、输出描述四、Java算法源码五、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》。 刷的越多…

Dockerfile构建LNMP镜像(yum方式)

目录 Dockerfile构建LNMP镜像 1、建立工作目录 2、编写Dockerfile文件 3、构建镜像 4、测试容器 5、浏览器访问测试&#xff1a; Dockerfile构建LNMP镜像 1、建立工作目录 [roothuyang1 ~]# mkdir lnmp/ [roothuyang1 ~]# cd lnmp/ 2、编写Dockerfile文件 [roothuyang1 …

【计算机网络】NAT及Bridge介绍

OSI七层模型 七层模型介绍及举例 为通过网络将人类可读信息通过网络从一台设备传输到另一台设备&#xff0c;必须在发送设备沿 OSI 模型的七层结构向下传输数据&#xff0c;然后在接收端沿七层结构向上传输数据。 数据在 OSI 模型中如何流动 库珀先生想给帕尔梅女士发一封电…

STSP中用于记录节点和旅行回路的四种数据结构

STSP中用于记录节点和旅行回路的四种数据结构 双链表结构2-level tree卫星结构k-level卫星结构树参考文献 对于TSP是是历史悠久的研究问题&#xff0c;直至现在已经有了很多成熟高效的算法来求解问题。在拥有好的求解算法的同时&#xff0c;优秀的数据结构可以同时大幅提升问题…

zabbix简易入门:基本的网络监控、WEB监控

我们越来越发现&#xff1a;网络越来越复杂&#xff0c;网络、应用、云端……故障点随时可能发生&#xff0c;而我们不能人工盯着所有的问题&#xff0c;所以&#xff0c;网管软件是必须的。那么没有预算的情况下&#xff0c;我们只好自己布署简单的网管软件了。 zabbix网站上可…

Requests模块怎么设置字符编码

Requests模块在发出网络请求时,支持设置字符编码,主要有以下两种方式: 在请求头中设置字符编码 可以通过headers参数设置字符编码,例如: import requestsurl http://www.example.comheaders {user-agent: my-app/0.0.1,Accept-Encoding: utf-8}resp requests.get(url, he…

什么是Java中的JVMTI(JVM Tool Interface)?

Java中的JNI&#xff08;Java Native Interface&#xff09;和JVMTI&#xff08;JVM Tool Interface&#xff09;都是与Java运行时环境&#xff08;JVM&#xff09;交互的工具&#xff0c;但它们有不同的目的和使用场景。下面我从新手的角度来幽默地解释一下它们的区别和用途。…

前端Vue入门-day08-vant组件库

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 vant 组件库 安装 导入 全部导入 按需导入 浏览器配饰 Viewport 布局 Rem 布局适配 vant 组件库 …