计算机通信之谜,带你彻底理解socket网络编程(二)
yuyutoo 2024-12-22 21:47 3 浏览 0 评论
在《计算机通信之谜,带你彻底理解socket网络编程(一)》一文里我们提到了,客户端发送了数据了之后,不管服务端还是客户端都close退出了,也就是说只能发送一次数据,这显然不符合实际的用途。那么该如何更改程序呢?
1、持续发送
要想实现持续发送,聪明的你可能想到了用循环,思路完全正确,但是循环多少次呢?实际的使用情况服务端一直都要运行,除非系统崩掉了,而客户端和服务端的长连接也要一直连着,除非客户端自己关闭了连接。所以我们的思路是双端都无限循环!所以可以用一个while(1)来循环,那么应该在哪里开始循环,这个要看自己具体的要求,如果要求三次握手只一次,随后发送数据的话进行持续发送。那么服务端和客户端的代码分别如下:
1.1、服务端代码
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define BUF_SIZE 512
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main()
{
//创建套接字
int m_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (m_sockfd < 0)
{
ERR_EXIT("create socket fail");
}
//初始化socket元素
struct sockaddr_in server_addr;
int server_len = sizeof(server_addr);
memset(&server_addr, 0, server_len);
server_addr.sin_family = AF_INET;
//server_addr.sin_addr.s_addr = inet_addr("0.0.0.0"); //用这个写法也可以
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(39002);
//绑定文件描述符和服务器的ip和端口号
int m_bindfd = bind(m_sockfd, (struct sockaddr *)&server_addr, server_len);
if (m_bindfd < 0)
{
ERR_EXIT("bind ip and port fail");
}
//进入监听状态,等待用户发起请求
int m_listenfd = listen(m_sockfd, 20);
if (m_listenfd < 0)
{
ERR_EXIT("listen client fail");
}
//定义客户端的套接字,这里返回一个新的套接字,后面通信时,就用这个m_connfd进行通信
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int m_connfd = accept(m_sockfd, (struct sockaddr *)&client_addr, &client_len);
printf("client accept success\n");
//接收客户端数据,并相应
char buffer[BUF_SIZE];
while (1)
{
memset(buffer, 0, sizeof(buffer)); //重置缓冲区
recv(m_connfd, buffer, sizeof(buffer)-1, 0);
printf("server recv:%s\n", buffer);
strcat(buffer, "+ACK");
send(m_connfd, buffer, sizeof(buffer)-1, 0);
}
//关闭套接字
close(m_connfd);
close(m_sockfd);
printf("server socket closed!!!\n");
return 0;
}
1.2、客户端代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 512
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main()
{
//创建套接字
int m_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (m_sockfd < 0)
{
ERR_EXIT("create socket fail");
}
//服务器的ip为本地,端口号
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("81.68.140.74");
server_addr.sin_port = htons(39002);
//向服务器发送连接请求
int m_connectfd = connect(m_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (m_connectfd < 0)
{
ERR_EXIT("connect server fail");
}
//发送并接收数据
char buffer[BUF_SIZE];
while (1)
{
memset(buffer, 0, sizeof(buffer)); //重置缓冲区
printf("client send:");
scanf("%s", buffer);
send(m_sockfd, buffer, sizeof(buffer)-1, 0);
recv(m_sockfd, buffer, sizeof(buffer)-1, 0);
printf("client recv:%s\n", buffer);
}
//断开连接
close(m_sockfd);
printf("client socket closed!!!\n");
return 0;
}
以上代码就可以正确的持续发送数据,代码已经在Linux主机上运行过,完全正确,如果有问题,欢迎评论说明。需要的童鞋可以拿去用。
2、服务端一直收到空包
那么以上代码有没有什么问题呢?如果亲自在linux主机上运行过的小伙伴可能会发现如果用Ctrl+C掐掉客户端或者kill掉客户端进程的话,服务端会不停的打印收到的数据,但是数据却是空字符串,GDB调试发现收到的实际上的0长度的空包(有时候持续收到多个空包后服务端就会断开连接)。也就是说
当客户端断开,服务端不停的接收到一个0字节
这个非常奇怪,客户端已经断开了,为什么服务端还会收到一个0字节的数据呢?这个问题我找了很久依旧没有找到一个合理的答案,网上的解释不是很明白,有懂的同学可以在评论区帮忙解释下。以下解释来自网络
一、用户正常退出,这时你还循环调用了Receive方法时会出现,并会陷入死循环。
二、用户非正常退出,包括拔网线,死机,服务器会捕捉到一个异常
问题的原因暂时无法找到,但是解决的办法却是有的,我们在收到客户端的数据的时候可以判断数据包的大小,如果为0的可能就是表示关闭客户端了。那么这时候服务端要做的就是判断收到的数据包的大小,如果是0的话就去close()客户端的连接,这样子就可以正常的接受和发送数据,客户端关闭了,服务端也不会有问题。修改后的while部分的代码如下
while (1)
{
if (m_connfd < 0)
{
m_connfd = accept(m_sockfd, (struct sockaddr *)&client_addr, &client_len);
printf("client accept success again!!!\n");
}
memset(buffer, 0, sizeof(buffer)); //重置缓冲区
int recvLen = recv(m_connfd, buffer, sizeof(buffer) - 1, 0);
if (recvLen <= 0)
{
close(m_connfd);
m_connfd = -1;
printf("client lose connection!!!\n");
continue;
}
printf("server recv:%s\n", buffer);
strcat(buffer, "+ACK");
send(m_connfd, buffer, sizeof(buffer) - 1, 0);
}
3、问题思考
以上的代码确实可以实现客户端持续发送数据,客户端断开与重连,服务端仍旧保持正常的过程。但是如果你以为socket网络编程就这么简单的话,那么你就真的太天真了。以上的客户端和服务端仅仅只是一对一的,就是说服务端就一个进程,客户端也就一个进程。但实际应用的过程中怎么可能这么简单呢?服务端可能就一台,但是客户端是多台的。
那这时候请同学动手做个实验,再启动一个客户端进程,你会发现服务端没反应,客户端发送数据的话,服务端也是没反应!那么该怎么办呢?聪明的你能够想到解决办法吗?欲知后事如何,请听下回分解!
更多精彩内容,请关注同名公众:一点笔记alittle
相关推荐
- 牛逼哄哄的数据库连接池,底层原理是个啥?
-
作者:敦格来源:https://blog.csdn.net/shuaihj/article/details/14223015这次我们采取技术演进的方式来谈谈数据库连接池的技术出现过程及其原理,以及当下...
- 如何实现一个连接池?一文带你深入浅出,彻底搞懂
-
-前言-【2w1h】是技术领域中一种非常有效的思考和学习方式,即What、Why和How;坚持【2w1h】,可以快速提升我们的深度思考能力。今天我们通过【2w1h】方式来讨论“连接池”:什么是连接...
- 什么是连接池?如何实现一个Java连接池?
-
什么是连接池?结构连接池对外提供接口:获得连接归还连接并暴露客户端可配置的参数:...
- 什么是数据库连接池? 什么是数据库连接池?如何使用
-
在JDBC编程中,每次创建和断开Connection对象都会消耗一定的时间和IO资源。这是因为在Java程序与数据库之间建立连接时,数据库端要验证用户名和密码,并且要为这个连接分配资源,Java程序则...
- 男生怎么插入对方话题,分享这2点,可以快速搭讪
-
在日常生活中,男生怎么插入对方的话题呢?来分享一下几个小妙招,让男生快速搭讪。1、同女性聊天.普通的女性可以先有礼貌的打招呼,询问对方聊天内容,对方的话题适不适合自己发表观点,如果不适合可以等着对方说...
- PDF文件如何插入、删除、替换、旋转页面
-
Hello,各位小伙伴们大家好,我是爱学习的小助手。...
- SQL——INSERT:插入数据 sqlinsert怎么插入
-
INSERT语句用于向数据库表中插入数据。1、插入一条数据insert into adm_user(login_name,password,name,mobile_no)&...
- 近乎变态的USB发展史,命名混乱到了极致,一篇文章让你读懂
-
USB3.0经历过多次改名,到最后没有任何规律!特别是USB3.0居然“升格”到USB3.2,搞得人头大!同1个蓝色USB口好像说3.0/3.1/3.2的都有,究竟谁对?今天为大家缕一缕。点赞收藏!【...
- 女大学生把30厘米“兔兔管”插入体内:上瘾性行为,害了多少人?
-
轻触关注,打开知识的大门,我在这里将与您共享更多精彩内容!女大学生小玲身高160厘米,体重却高达170斤。她曾尝试过节许多减肥方法,但无一例外均以失败告终。...
- 大年初二铁棍插入10岁女童脑内,上海值班医生手术取出
-
澎湃新闻记者陈斯斯来自浙江嘉兴的10岁女童逛商场遭遇意外,一根铁棍插入其脑内,随后被120急救车急送上海交大医学院附属新华医院(简称上海新华医院)实施急诊手术。2月3日,澎湃新闻(www.thep...
- 插入语和状语的区别 where引导的定语从句和状语从句的区别
-
1.插入语是在一个句子中插入一个成分,它不是句子的成分,而是表示说话人的态度或进行解释补充说明等。...
- 用户数据之存量——DAU/MAU 用户数据到底是什么
-
编辑导语:数据分析是设计师了解用户行为的一个重要手段。本文作者分享了不同指标的核心含义,从用户数据的存量、Active活跃度、User用户展开分析,一起来学习一下吧,希望对你有帮助。随着设计师对产品设...
- 键盘上这些被厂商抠掉的功能键,到底有多没用?
-
最近呢,办公室流行起了客制化键盘这个东西,简单来讲就是可以根据自己的需求和喜好来定制独一无二的键盘。...
- 如何制作Windows系统安装U盘 最简单的方法 有手就行
-
前段时间一位朋友问我,能不能给他安装系统,他随后补了一句:给你钱。其实安装Windows系统是一件非常简单的事情,所以我想着,不如告诉大家最简单的操作方法,让大家能够省一笔安装系统的钱。该教程会分为两...
- 大模型给你的答案,也要插入广告了?
-
英伟达创始人CEO黄仁勋曾在被问到他是否使用ChatGPT或Bard时,他回答称,“我一般用Perplexity,而且几乎每天都在用。”Perplexity旨在用AI技术打造一个没有广告的“谷歌搜索...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)