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

相关文章

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Mysql表的简单操作(基本技能)

《Mysql表的简单操作(基本技能)》在数据库中,表的操作主要包括表的创建、查看、修改、删除等,了解如何操作这些表是数据库管理和开发的基本技能,本文给大家介绍Mysql表的简单操作,感兴趣的朋友一起看... 目录3.1 创建表 3.2 查看表结构3.3 修改表3.4 实践案例:修改表在数据库中,表的操作主要

springboot简单集成Security配置的教程

《springboot简单集成Security配置的教程》:本文主要介绍springboot简单集成Security配置的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录集成Security安全框架引入依赖编写配置类WebSecurityConfig(自定义资源权限规则

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

如何使用Python实现一个简单的window任务管理器

《如何使用Python实现一个简单的window任务管理器》这篇文章主要为大家详细介绍了如何使用Python实现一个简单的window任务管理器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 任务管理器效果图完整代码import tkinter as tkfrom tkinter i

使用Python高效获取网络数据的操作指南

《使用Python高效获取网络数据的操作指南》网络爬虫是一种自动化程序,用于访问和提取网站上的数据,Python是进行网络爬虫开发的理想语言,拥有丰富的库和工具,使得编写和维护爬虫变得简单高效,本文将... 目录网络爬虫的基本概念常用库介绍安装库Requests和BeautifulSoup爬虫开发发送请求解

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

使用EasyExcel实现简单的Excel表格解析操作

《使用EasyExcel实现简单的Excel表格解析操作》:本文主要介绍如何使用EasyExcel完成简单的表格解析操作,同时实现了大量数据情况下数据的分次批量入库,并记录每条数据入库的状态,感兴... 目录前言固定模板及表数据格式的解析实现Excel模板内容对应的实体类实现AnalysisEventLis