【计算机网络】模拟一个基于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

相关文章

一份LLM资源清单围观技术大佬的日常;手把手教你在美国搭建「百万卡」AI数据中心;为啥大模型做不好简单的数学计算? | ShowMeAI日报

👀日报&周刊合集 | 🎡ShowMeAI官网 | 🧡 点赞关注评论拜托啦! 1. 为啥大模型做不好简单的数学计算?从大模型高考数学成绩不及格说起 司南评测体系 OpenCompass 选取 7 个大模型 (6 个开源模型+ GPT-4o),组织参与了 2024 年高考「新课标I卷」的语文、数学、英语考试,然后由经验丰富的判卷老师评判得分。 结果如上图所

【Altium】查找PCB上未连接的网络

【更多软件使用问题请点击亿道电子官方网站】 1、文档目标: PCB设计后期检查中找出没有连接的网络 应用场景:PCB设计后期,需要检查是否所有网络都已连接布线。虽然未连接的网络会有飞线显示,但是由于布线后期整板布线密度较高,虚连,断连的网络用肉眼难以轻易发现。用DRC检查也可以找出未连接的网络,如果PCB中DRC问题较多,查找起来就不是很方便。使用PCB Filter面板来达成目的相比DRC

通信系统网络架构_2.广域网网络架构

1.概述          通俗来讲,广域网是将分布于相比局域网络更广区域的计算机设备联接起来的网络。广域网由通信子网于资源子网组成。通信子网可以利用公用分组交换网、卫星通信网和无线分组交换网构建,将分布在不同地区的局域网或计算机系统互连起来,实现资源子网的共享。 2.网络组成          广域网属于多级网络,通常由骨干网、分布网、接入网组成。在网络规模较小时,可仅由骨干网和接入网组成

Toolbar+DrawerLayout使用详情结合网络各大神

最近也想搞下toolbar+drawerlayout的使用。结合网络上各大神的杰作,我把大部分的内容效果都完成了遍。现在记录下各个功能效果的实现以及一些细节注意点。 这图弹出两个菜单内容都是仿QQ界面的选项。左边一个是drawerlayout的弹窗。右边是toolbar的popup弹窗。 开始实现步骤详情: 1.创建toolbar布局跟drawerlayout布局 <?xml vers

回调的简单理解

之前一直不太明白回调的用法,现在简单的理解下 就按这张slidingmenu来说,主界面为Activity界面,而旁边的菜单为fragment界面。1.现在通过主界面的slidingmenu按钮来点开旁边的菜单功能并且选中”区县“选项(到这里就可以理解为A类调用B类里面的c方法)。2.通过触发“区县”的选项使得主界面跳转到“区县”相关的新闻列表界面中(到这里就可以理解为B类调用A类中的d方法

自制的浏览器主页,可以是最简单的桌面应用,可以把它当成备忘录桌面应用

自制的浏览器主页,可以是最简单的桌面应用,可以把它当成备忘录桌面应用。如果你看不懂,请留言。 完整代码: <!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><ti

探索蓝牙协议的奥秘:用ESP32实现高质量蓝牙音频传输

蓝牙(Bluetooth)是一种短距离无线通信技术,广泛应用于各种电子设备之间的数据传输。自1994年由爱立信公司首次提出以来,蓝牙技术已经经历了多个版本的更新和改进。本文将详细介绍蓝牙协议,并通过一个具体的项目——使用ESP32实现蓝牙音频传输,来展示蓝牙协议的实际应用及其优点。 蓝牙协议概述 蓝牙协议栈 蓝牙协议栈是蓝牙技术的核心,定义了蓝牙设备之间如何进行通信。蓝牙协议

python实现最简单循环神经网络(RNNs)

Recurrent Neural Networks(RNNs) 的模型: 上图中红色部分是输入向量。文本、单词、数据都是输入,在网络里都以向量的形式进行表示。 绿色部分是隐藏向量。是加工处理过程。 蓝色部分是输出向量。 python代码表示如下: rnn = RNN()y = rnn.step(x) # x为输入向量,y为输出向量 RNNs神经网络由神经元组成, python

基于 Java 实现的智能客服聊天工具模拟场景

服务端代码 import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;public class Serv

【杂记-浅谈DHCP动态主机配置协议】

DHCP动态主机配置协议 一、DHCP概述1、定义2、作用3、报文类型 二、DHCP的工作原理三、DHCP服务器的配置和管理 一、DHCP概述 1、定义 DHCP,Dynamic Host Configuration Protocol,动态主机配置协议,是一种网络协议,主要用于在IP网络中自动分配和管理IP地址以及其他网络配置参数。 2、作用 DHCP允许计算机和其他设备通