tcp是基于字节流的,udp是基于报文即数据包的,所以tcp会产生一个叫做粘包的问题,而udp不会产生。

我们这节主要讨论粘包问题:

先看一下粘包问题的原因:

总结如下:

1、应用进程的缓冲区和Socket缓冲区的大小不一定相吻合。

2、tcp传输段有mss限制。

3、链路层有个mtu限制。

粘包的解决方案:

1、设置定长包,定长接受。

2、在包尾加上\r\n,例如ftp就是这样实现的。

3、包头加上包体长度。

4、更复杂的应用层协议。

下面就封装一下readn和writen来实现解决粘包问题,改进一下先前的那个回射程序:

服务器端:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) struct packet //新定义包的结构,包体长度和包体
{
int len;
char buf[1024];
}; ssize_t readn(int fd, void *buf, size_t count) //readn的封装
{
size_t nleft = count;
ssize_t nread;
char *bufp = (char*)buf; while (nleft > 0)
{
if ((nread = read(fd, bufp, nleft)) < 0)//这里小于零两种情况一种是信号打断一种就是失败
{
if (errno == EINTR) //可能是信号打断
continue;
return -1; //失败
}
else if (nread == 0)
return count - nleft; bufp += nread;
nleft -= nread;
} return count;
} ssize_t writen(int fd, const void *buf, size_t count)//writen的封装
{
size_t nleft = count;
ssize_t nwritten;
char *bufp = (char*)buf; while (nleft > 0)
{
if ((nwritten = write(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
continue;
return -1;
}
else if (nwritten == 0)
continue; bufp += nwritten;
nleft -= nwritten;
} return count;
} void do_service(int conn)
{
struct packet recvbuf;
int n;
while (1)
{
memset(&recvbuf, 0, sizeof(recvbuf));
int ret = readn(conn, &recvbuf.len, 4); //先接受包头长度
if (ret == -1)
ERR_EXIT("read");
else if (ret < 4)
{
printf("client close\n");
break;
} n = ntohl(recvbuf.len); //这里有个字节序的转换,因为是要在网络上传输
ret = readn(conn, recvbuf.buf, n); //再接受包体
if (ret == -1)
ERR_EXIT("read");
else if (ret < n)
{
printf("client close\n");
break;
} fputs(recvbuf.buf, stdout);
writen(conn, &recvbuf, 4+n); //回写要包体长度加包体
}
} int main(void)
{
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
/* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
/*inet_aton("127.0.0.1", &servaddr.sin_addr);*/ int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt"); if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen"); struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn; pid_t pid;
while (1)
{
if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
ERR_EXIT("accept"); printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid = fork();
if (pid == -1)
ERR_EXIT("fork");
if (pid == 0)
{
close(listenfd);
do_service(conn);
exit(EXIT_SUCCESS);
}
else
close(conn);
} return 0;
}

客户端:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) struct packet
{
int len;
char buf[1024];
}; ssize_t readn(int fd, void *buf, size_t count)
{
size_t nleft = count;
ssize_t nread;
char *bufp = (char*)buf; while (nleft > 0)
{
if ((nread = read(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
continue;
return -1;
}
else if (nread == 0)
return count - nleft; bufp += nread;
nleft -= nread;
} return count;
} ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten;
char *bufp = (char*)buf; while (nleft > 0)
{
if ((nwritten = write(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
continue;
return -1;
}
else if (nwritten == 0)
continue; bufp += nwritten;
nleft -= nwritten;
} return count;
}
int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("connect"); struct packet sendbuf;
struct packet recvbuf;
memset(&sendbuf, 0, sizeof(sendbuf));
memset(&recvbuf, 0, sizeof(recvbuf));
int n;
while (fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)
{
n = strlen(sendbuf.buf);
sendbuf.len = htonl(n);
writen(sock, &sendbuf, 4+n); int ret = readn(sock, &recvbuf.len, 4);
if (ret == -1)
ERR_EXIT("read");
else if (ret < 4)
{
printf("client close\n");
break;
} n = ntohl(recvbuf.len);
ret = readn(sock, recvbuf.buf, n);
if (ret == -1)
ERR_EXIT("read");
else if (ret < n)
{
printf("client close\n");
break;
} fputs(recvbuf.buf, stdout);
memset(&sendbuf, 0, sizeof(sendbuf));
memset(&recvbuf, 0, sizeof(recvbuf));
} close(sock); return 0;
}

这种解决粘包的方法特别实在网络中传输的时候十分必要。

09Socket编程的更多相关文章

  1. Python语言系列-09-socket编程

    简介 软件开发的架构 1.C/S架构(client-server) 2.B/S架构 (browser-server) 网络基础概念 网络三要素: 1.ip 2.port 3.通信协议:TCP.UDP ...

  2. 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代

    2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...

  3. JavaScript之父Brendan Eich,Clojure 创建者Rich Hickey,Python创建者Van Rossum等编程大牛对程序员的职业建议

    软件开发是现时很火的职业.据美国劳动局发布的一项统计数据显示,从2014年至2024年,美国就业市场对开发人员的需求量将增长17%,而这个增长率比起所有职业的平均需求量高出了7%.很多人年轻人会选择编 ...

  4. 读书笔记:JavaScript DOM 编程艺术(第二版)

    读完还是能学到很多的基础知识,这里记录下,方便回顾与及时查阅. 内容也有自己的一些补充. JavaScript DOM 编程艺术(第二版) 1.JavaScript简史 JavaScript由Nets ...

  5. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  6. C#异步编程(一)

    异步编程简介 前言 本人学习.Net两年有余,是第一次写博客,虽然写的很认真,当毕竟是第一次,肯定会有很多不足之处, 希望大家照顾照顾新人,有错误之处可以指出来,我会虚心接受的. 何谓异步 与同步相对 ...

  7. UE4新手之编程指南

    虚幻引擎4为程序员提供了两套工具集,可共同使用来加速开发的工作流程. 新的游戏类.Slate和Canvas用户接口元素以及编辑器功能可以使用C++语言来编写,并且在使用Visual Studio 或 ...

  8. C#与C++的发展历程第三 - C#5.0异步编程巅峰

    系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...

  9. 猫哥网络编程系列:HTTP PEM 万能调试法

    注:本文内容较长且细节较多,建议先收藏再阅读,原文将在 Github 上维护与更新. 在 HTTP 接口开发与调试过程中,我们经常遇到以下类似的问题: 为什么本地环境接口可以调用成功,但放到手机上就跑 ...

随机推荐

  1. 【ajax】xhr

    jQuery xhr: function() { return window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP&quo ...

  2. dos快速通道

    要在文件夹的右键菜单中添加“命令提示符”选项.在注册表HKEY_CLASSES_ROOT\Directory\shell分支下新建一项“CommandPrompt”,修改右侧窗格中的“默认”键值为“命 ...

  3. 2.0.4 CCLabelTTF在ios7下不显示的问题

    要修改框架lib目录里的 CCImage.mm,就是把浮点值取了个整,之前的浮点形导致绘制失败: 在_initWithString这个方法里把如下代码替换一下就行了: //////////////// ...

  4. Python的类实例属性访问规则

    一般来说,在Python中,类实例属性的访问规则算是比较直观的. 但是,仍然存在一些不是很直观的地方,特别是对C++和Java程序员来说,更是如此. 在这里,我们需要明白以下几个地方: 1.Pytho ...

  5. win7建立无线wifi热点的几个常见的问题

    命令行开启WiFi方法: 开启WiFi.bat netsh wlan set hostednetwork mode=allow netsh wlan set hostednetwork ssid=ss ...

  6. 简单选择排序算法(C++版)

    #include <iostream> using namespace std; /** Simple Select Sort * brief: * Key: * * position: ...

  7. Win+R运行自定义程序应该这样玩

    互联网是一个“生态圈”,Windows有自己的生态学,有些看似高效的“奇技淫巧”实则只是搞笑. 我很以前十分崇拜善用佳软的站长,对事不对人,有些弊端,只是我们不知道,但并不代表就没有. 有些“恍然大悟 ...

  8. JavaScript的一些认识

    最近看了一下<nodejs开发指南>发现nodejs在某些特定的领域由他自己的长处,适合密集计算但是业务逻辑比较简单的场景,如果做网站还是选择php吧,呵呵,这本书我除了第5章<用n ...

  9. C++ Primer : 第九章 : 顺序容器的定义、迭代器以及赋值与swap

    顺序容器属于C++ STL的一部分,也是非常重要的一部分. 顺序容器包括: std::vector,包含在头文件<vector>中 std::string, 包含在头文件<strin ...

  10. UVa 1592 数据库(c++pair)

    Input Input contains several datasets. The first line of each dataset contains two integer numbersn  ...