【计算机网络】模拟一个基于TCP协议的简单的阻塞式的网络聊天工具

本文主要是介绍【计算机网络】模拟一个基于TCP协议的简单的阻塞式的网络聊天工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、socket API 常用函数

这些函数都在sys/socket.h中。

1.1 socket()
 #include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int socket(int domain, int type, int protocol);

domain

       Name                Purpose                          Man pageAF_UNIX, AF_LOCAL   Local communication              unix(7)AF_INET             IPv4 Internet protocols          ip(7)AF_INET6            IPv6 Internet protocols          ipv6(7)AF_IPX              IPX - Novell protocolsAF_NETLINK          Kernel user interface device     netlink(7)AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)AF_AX25             Amateur radio AX.25 protocolAF_ATMPVC           Access to raw ATM PVCsAF_APPLETALK        Appletalk                        ddp(7)AF_PACKET           Low level packet interface       packet(7)

type

SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based byte streams.  An out-of-band data transmission mecha-nism may be supported.SOCK_DGRAM      Supports datagrams (connectionless, unreliable messages of a fixed maximum length).SOCK_SEQPACKET  Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximumlength; a consumer is required to read an entire packet with each input system call.SOCK_RAW        Provides raw network protocol access.SOCK_RDM        Provides a reliable datagram layer that does not guarantee ordering.SOCK_PACKET     Obsolete and should not be used in new programs; see packet(7).

注意:

  • socket打开一个网络通讯端口,如果成功的话就像read一样返回一个文件描述符。
  • 应用程序可以向读写文件一样用read/write在网络上收发数据。
  • 如果socket调用出错则返回-1。
  • 对于IPv4,family参数指定为AF_INET。
  • 对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。
  • protocol参数的介绍从略,指定为0即可。
1.2 bind()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数

When a socket is created with socket(2), it exists in a name space (address family) but has no address assigned to it.  bind()
assigns the address specified to by addr to the socket referred to by the file descriptor sockfd.  addrlen specifies the size,
in  bytes,  of  the  address  structure  pointed  to  by addr.  Traditionally, this operation is called “assigning a name to a socket”.It is normally necessary to assign a local address using bind() before a  SOCK_STREAM  socket  may  receive  connections  (see accept(2)).The  rules  used in name binding vary between address families.  Consult the manual entries in Section 7 for detailed information.  For AF_INET see ip(7), for AF_INET6 see ipv6(7), for AF_UNIX see unix(7), for AF_APPLETALK see  ddp(7),  for  AF_PACKET see packet(7), for AF_X25 see x25(7) and for AF_NETLINK see netlink(7).

注意:

  • 服务器程序所监听的⺴络地址和端⼝号通常是固定不变的,客户端程序得知服务器程序的地址和端⼝号后就可以向服务器发起连接; 服务器需要调⽤bind绑定⼀个固定的⺴络地址和端⼝号;
  • bind()成功返回0,失败返回-1。
  • bind()的作⽤是将参数sockfd和myaddr绑定在⼀起, 使sockfd这个⽤于网络通讯的⽂件描述符监听myaddr所描述的地址和端⼝号;
  • struct sockaddr *是⼀个通⽤指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,⽽它们的⻓度各不相同,所以需要第三个参数addrlen指定结构体的⻓度;
1.3 listen()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int listen(int sockfd, int backlog);

参数:

listen() marks the socket referred to by sockfd as a passive socket, that is, as a socket that will be used to accept incoming connection requests using accept(2).The sockfd argument is a file descriptor that refers to a socket of type SOCK_STREAM or SOCK_SEQPACKET.The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow.  If  a  connection  request  arrives  when  the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds.

注意:

  • listen()声明socket处于监听状态,并且做多允许有backlog个客户端处于连接等待状态。
  • listen()成功返回0,失败返回-1。
1.4 accept()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:

sockfd:socket文件描述符
addr:sockaddr类型结构体
addrlen:sockaddr类型结构体大小

注意:

  • 三次握⼿完成后, 服务器调⽤accept()接受连接;
  • 如果服务器调⽤accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr是⼀个传出参数,accept()返回时传出客户端的地址和端⼝号;
  • 如果给addr 参数传NULL,表⽰不关⼼客户端的地址;
  • addrlen参数是⼀个传⼊传出参数(value-result argument), 传⼊的是调⽤者提供的, 缓冲区addr的⻓度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际⻓度(有可能没有占满调⽤者提供的缓冲区);

2、服务器端

2.1 基本步骤:
  1. 创建socket
  2. 绑定端口号
  3. 把socket转换成被动模式(listen)
  4. 循环的进行accept
  5. 从accept返回的new_fd之中读取客户端的请求
  6. 根据读到的请求进行计算和处理
  7. 把处理后的结果返回给客户端
2.2 源码:
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>#define _PORT_ 9999
#define _BACKLOG_ 10int main(){//创建socket文件描述符(TCP  客户端 + 服务器)int sock = socket(AF_INET,SOCK_STREAM,0);if(sock < 0){printf("create socket error,error : %d , error string : %s \n",errno,strerror(errno));}//sockaddr结构(地址类型 端口号 IP地址)struct sockaddr_in server_socket;struct sockaddr_in client_socket;//初始化server_socketbzero(&server_socket,sizeof(server_socket));server_socket.sin_family = AF_INET;//INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或"所有地址"、"任意地址"。server_socket.sin_addr.s_addr = htonl(INADDR_ANY);server_socket.sin_port = htons(_PORT_);//绑定端口号if(bind(sock,(struct sockaddr*)&server_socket,sizeof(struct sockaddr_in)) < 0){//绑定失败printf("bind error,errno: %d , error string: %s \n",errno,strerror(errno));close(sock);return 1;}//开始监听socket//_BACKLOG_   最多可监听10个if(listen(sock,_BACKLOG_) < 0){printf("lisen error,errno: %d , error string: %s \n",errno,strerror(errno));close(sock);return 2;}printf("bind and listen success! wait accept...\n");//循环接收客户端的请求while(1){socklen_t len = 0;int client_sock = accept(sock,(struct sockaddr *)&client_socket,&len);if(client_sock < 0){printf("accept error,client ip: %s , error: %d , error string: %s \n",inet_ntoa(client_socket.sin_addr),errno,strerror(errno));close(sock);return 3;}//从accept返回的client_sock中读取客户端的请求char buf_ip[INET_ADDRSTRLEN] = {0};//将客户端IP地址转换成字符串存入缓冲区buf_ip中inet_ntop(AF_INET,&client_socket.sin_addr,buf_ip,sizeof(buf_ip));printf("[connect] ip: %s ,port: %d\n",buf_ip,ntohs(client_socket.sin_port));while(1){//根据读取到的请求进行计算和处理//这里实现一个简单的回显服务器,对客户端请求不作任何处理,直接输出char buf[1024] = {0};read(client_sock,buf,sizeof(buf));printf("client :# %s\n",buf);printf("server :$ ");//把处理后的结果发送给客户端memset(buf,'\0',sizeof(buf));fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = '\0';write(client_sock,buf,strlen(buf)+1);printf("please wait...\n");}}close(sock);return 0;}

3、客户端

3.1 基本步骤:
  1. 创建socket
  2. 和服务器建立连接
  3. 给服务器发送数据
  4. 从服务器读取返回的结果
  5. 和服务器断开连接
3.2 源码:
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>#define SERVER_PORT 9999
#define SERVER_IP "192.168.11.128"int main(int argc,char *argv[]){if(argc != 2){printf("Usage:client IP\n");return 1;}char *str = argv[1];char buf[1024];memset(buf,'\0',sizeof(buf));struct sockaddr_in server_sock;int sock = socket(AF_INET,SOCK_STREAM,0);bzero(&server_sock,sizeof(server_sock));server_sock.sin_family = AF_INET;//字符串SERVER_IP转成in_addr server_sockinet_pton(AF_INET,SERVER_IP,&server_sock.sin_addr);server_sock.sin_port = htons(SERVER_PORT);int ret = connect(sock,(struct sockaddr *)&server_sock,sizeof(server_sock));if(ret<0){printf("connect failed ...,errno is : %d,errstring is: %s\n",errno,strerror(errno));return 1;}printf("connect success...\n");while(1){printf("client:#");fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = '\0';write(sock,buf,sizeof(buf));if(strncasecmp(buf,"quit",4) == 0){printf("quit!\n");break;}printf("please wait ...\n");read(sock,buf,sizeof(buf));printf("server :$ %s\n",buf);}close(sock);return 0;
}
3.3 注意:
  • 由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。
  • 客户端没有必要非要绑定端口号,否则如果在一台机器上启动多个客户端,就会出现端口号被占用,导致不能正确建立连接。
  • 服务器端如果不绑定端口号的话,内核会给服务器端自动分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇见麻烦。
  • 客户端需要调用connect()连接服务器。connect和bind的参数形式一样,区别在于bind的参数是自己的地址,connect的参数是对方的地址。
  • connect调用失败的几个可能的原因:服务器没有关闭防火墙,服务器没启动、地址错误、端口错误、类型错误等。

4、程序运行结果

服务器:
这里写图片描述

客户端:
这里写图片描述

查看监听状态:

这里写图片描述

这里写图片描述

5、缺陷

5.1 测试多个连接的情况

客户端1:

这里写图片描述

客户端2:

这里写图片描述

服务器:

这里写图片描述

这时我们发现第二个客户端并不能正确的和服务器建立连接。

5.2 原因分析:

是因为我们accecpt了⼀个请求之后, 就在⼀直while循环尝试read, 没有继续调⽤到accecpt, 导致不能接受新的请求。

我们当前的这个TCP, 只能处理⼀个连接。

改进方法:

想要服务器可以处理多个客户端的请求,可以依靠多线程/多进程/生产者消费者模型来实现。

这篇关于【计算机网络】模拟一个基于TCP协议的简单的阻塞式的网络聊天工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/615295

相关文章

HTTP 与 SpringBoot 参数提交与接收协议方式

《HTTP与SpringBoot参数提交与接收协议方式》HTTP参数提交方式包括URL查询、表单、JSON/XML、路径变量、头部、Cookie、GraphQL、WebSocket和SSE,依据... 目录HTTP 协议支持多种参数提交方式,主要取决于请求方法(Method)和内容类型(Content-Ty

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

Python 基于http.server模块实现简单http服务的代码举例

《Python基于http.server模块实现简单http服务的代码举例》Pythonhttp.server模块通过继承BaseHTTPRequestHandler处理HTTP请求,使用Threa... 目录测试环境代码实现相关介绍模块简介类及相关函数简介参考链接测试环境win11专业版python

python连接sqlite3简单用法完整例子

《python连接sqlite3简单用法完整例子》SQLite3是一个内置的Python模块,可以通过Python的标准库轻松地使用,无需进行额外安装和配置,:本文主要介绍python连接sqli... 目录1. 连接到数据库2. 创建游标对象3. 创建表4. 插入数据5. 查询数据6. 更新数据7. 删除

Jenkins的安装与简单配置过程

《Jenkins的安装与简单配置过程》本文简述Jenkins在CentOS7.3上安装流程,包括Java环境配置、RPM包安装、修改JENKINS_HOME路径及权限、启动服务、插件安装与系统管理设置... 目录www.chinasem.cnJenkins安装访问并配置JenkinsJenkins配置邮件通知

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块

Java对接MQTT协议的完整实现示例代码

《Java对接MQTT协议的完整实现示例代码》MQTT是一个基于客户端-服务器的消息发布/订阅传输协议,MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛,:本文主要介绍Ja... 目录前言前置依赖1. MQTT配置类代码解析1.1 MQTT客户端工厂1.2 MQTT消息订阅适配器1.

Go语言网络故障诊断与调试技巧

《Go语言网络故障诊断与调试技巧》在分布式系统和微服务架构的浪潮中,网络编程成为系统性能和可靠性的核心支柱,从高并发的API服务到实时通信应用,网络的稳定性直接影响用户体验,本文面向熟悉Go基本语法和... 目录1. 引言2. Go 语言网络编程的优势与特色2.1 简洁高效的标准库2.2 强大的并发模型2.

Linux中的自定义协议+序列反序列化用法

《Linux中的自定义协议+序列反序列化用法》文章探讨网络程序在应用层的实现,涉及TCP协议的数据传输机制、结构化数据的序列化与反序列化方法,以及通过JSON和自定义协议构建网络计算器的思路,强调分层... 目录一,再次理解协议二,序列化和反序列化三,实现网络计算器3.1 日志文件3.2Socket.hpp

Linux中的HTTPS协议原理分析

《Linux中的HTTPS协议原理分析》文章解释了HTTPS的必要性:HTTP明文传输易被篡改和劫持,HTTPS通过非对称加密协商对称密钥、CA证书认证和混合加密机制,有效防范中间人攻击,保障通信安全... 目录一、什么是加密和解密?二、为什么需要加密?三、常见的加密方式3.1 对称加密3.2非对称加密四、