我记得最开始接触网络程序是在我读大二的时候,当时我做的是一个聊天的程序,也不知道服务器和客户端的概念,在网上就是一顿找啊,才到自己能看懂的答案,但是只能两个程序能聊天。造成这样的原因是程序是阻塞的,然而那时我并不知道什么是阻塞,所以程序就搁置到那了。这个问题一直到我大四在一家音视频公司实习的时候才解决了,也是我对网络程序了解更深了一步。结合我自己对网络编程的经验,写了关于网络程序的篇章,一共有三篇。之所以想写下来,是因为想着我在学网络程序初期走了很多不必要的弯路,如果有正在想学网络程序的同僚,希望这篇文章能让你少走弯路。

废话不多说,开始听我洗脑。哈哈

所谓网络程序,就是写的程序能通过网络进行通信(就是交互数据),所以要想程序间能进行通信,至少要运行两个实例(其中,一个实例为服务器,一个为客户端)。一般来说,服务器实例只要一个,客户端实例可以多个。服务器和客户端的实例可以是同一个程序(该程序既是服务器,又是客户端,一般用于局域网),也可以是不同的程序(服务器一个程序,客户端一个程序)。

如何建立TCP链接?(相关代码都使用C++进行演示)

首先,个人建议先了解一下TCP协议,以及TCP链接建立的三次握手、断开的四次挥手。期间服务器和客户端状态变化建议了解比较好,这里不做讲解,感兴趣的可以去看看。

如果你不想了解那些原理,也没关系,并不影响编程。

服务端(只要记住六步):

1)创建套接字(socket)

2)绑定地址(bind)

3)监听(listen)

4)轮询等待客户端的接入(select | accept)

5)接收,发送消息(recv | send)

6)关闭(close)

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <winsock2.h>
#pragma comment(lib, “ws2_32.lib”)

int main()
{
//检测版本号
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(1, 1);
::WSAStartup(wVersionRequested, &wsaData);

//1)创建套接字
SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP0);//2)绑定地址
SOCKADDR_IN addrServer;
addrServer.sin_family = AF_INET;
addrServer.sin_port = ::htons(8888);
addrServer.sin_addr.S_un.S_addr = ::inet_addr("127.0.0.1");
::bind(s, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));//3)监听
::listen(s, 5);//4)轮询等待客户端的接入
int iLen = sizeof(SOCKADDR);
while (true) {SOCKADDR_IN clientAddr;SOCKET client = ::accept(s, (SOCKADDR *)&clientAddr, &iLen);//5)发送消息::send(client, "sever", 6, 0);
}//6)关闭
closesocket(s);
return 0;

}

客户端(只要记住四步)

1)创建套接字(scoket)

2)连接服务器(connect)

3)接收,发送消息(recv | send)

4)关闭(close)

#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")int main()
{//检测版本号WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD(1, 1);::WSAStartup(wVersionRequested, &wsaData);//1)创建套接字SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//2)连接SOCKADDR_IN addrServer;addrServer.sin_family = AF_INET;addrServer.sin_port = ::htons(8888);addrServer.sin_addr.S_un.S_addr = ::inet_addr("127.0.0.1");::connect(s, (sockaddr*)&addrServer, sizeof(addrServer));//3)接收消息char szBuf[1024] = { 0 };int ret = recv(s, szBuf, 1024, 0);szBuf[ret] = '\0';printf("%s\n", szBuf);//4)关闭closesocket(s);return 0;
}

上述代码是基于WinSocket编写的,visual studio编辑可直接运行。为了便于理解,很多异常情况没有判断。 看了上面客户端,和服务器的代码是不是觉得很容易?但是上诉程序只能保证一个客户端进行正常交互。为什么呢?原因就是服务器中的accept函数是阻塞函数,也就是当有新的客户端连接到服务器时,accept才会返回,否则一直处于等待状态。那么如何解决呢?这里可能很多同僚也知道,使用select模式轮询,或者epoll模式(Linux独有)。这里给大家科普一下,早期还没有select模式和epoll模式的时候,是如何解决阻塞的呢?是用线程处理的,当时最早的魔兽世界服务器据说是用线程处理的阻塞问题。因为accept,recv,send三个函数都是阻塞函数,所以服务端至少创建2倍的客户端+1的线程数量。这是在没有select和epoll模式的做法。但是线程多了,不仅消耗操作系统的资源,而且会让程序变得很复杂,不是很好维护。下面给出select模式下的,服务端和客户端代码。

服务端:

#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")bool IsCanWrite(SOCKET s)
{fd_set writefds;FD_ZERO(&writefds);FD_SET(s, &writefds);timeval timeout;timeout.tv_sec = 0;timeout.tv_usec = 0;int ret = select(FD_SETSIZE, NULL, &writefds, NULL, &timeout);if (ret > 0 && FD_ISSET(s, &writefds)) {return true;}return false;
}int main()
{//检测版本号WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD(1, 1);::WSAStartup(wVersionRequested, &wsaData);//1)创建套接字SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//2)绑定地址SOCKADDR_IN addrServer;addrServer.sin_family = AF_INET;addrServer.sin_port = ::htons(8888);addrServer.sin_addr.S_un.S_addr = ::inet_addr("127.0.0.1");::bind(s, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));//3)监听::listen(s, 5);//4)轮询等待客户端的接入SOCKET sMax = s;int iLen = sizeof(SOCKADDR);while (true) {//判断是否有新的连接fd_set fdRead;FD_ZERO(&fdRead);FD_SET(s, &fdRead);int ret = -1;timeval t_out = { 0, 0 };ret = select(sMax + 1, &fdRead, NULL, NULL, &t_out);if (ret > 0) {if (FD_ISSET(s, &fdRead)) {SOCKADDR_IN clientAddr;SOCKET client = ::accept(s, (SOCKADDR *)&clientAddr, &iLen);//5)发送消息//判断套接字是否可写if (IsCanWrite(client)) {::send(client, "sever", 6, 0);}}}//防止独占CPUSleep(10);}//6)关闭closesocket(s);return 0;
}

客户端:

#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")bool IsCanRead(SOCKET s)
{fd_set readfds;FD_ZERO(&readfds);FD_SET(s, &readfds);timeval timeout;timeout.tv_sec = 0;timeout.tv_usec = 0;int ret = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout);if (ret > 0 && FD_ISSET(s, &readfds)) {return true;}return false;
}int main()
{//检测版本号WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD(1, 1);::WSAStartup(wVersionRequested, &wsaData);//1)创建套接字SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//2)连接SOCKADDR_IN addrServer;addrServer.sin_family = AF_INET;addrServer.sin_port = ::htons(8888);addrServer.sin_addr.S_un.S_addr = ::inet_addr("127.0.0.1");::connect(s, (sockaddr*)&addrServer, sizeof(addrServer));//3)接收消息while (true) {//判断套接字是否可读if (IsCanRead(s)) {char szBuf[1024] = { 0 };int ret = recv(s, szBuf, 1024, 0);szBuf[ret] = '\0';printf("%s\n", szBuf);}//防止独占CPUSleep(10);}//4)关闭closesocket(s);return 0;
}

上述就是阻塞模式和非阻塞模式的代码,这里非阻塞模式只给了select模型,至于epoll模型,可以自行科普(epoll和select模型是有区别,根据不同的情况选择不同的模式,这里不做说明)。

如何建立UDP链接?(相关代码都使用C++进行演示)

还是个人建议先了解一下UDP协议,相对于TCP协议比较而言,UDP协议简单的多。

如果你不想了解那些原理,也没关系,并不影响编程。

服务端(只要记住四步):

1)创建套接字(socket)

2)绑定地址(bind)

3)接收,发送消息(recv | send)

4)关闭(close)

#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")int main()
{//检测版本号WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD(1, 1);::WSAStartup(wVersionRequested, &wsaData);//1)创建套接字SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);//2)绑定地址SOCKADDR_IN addrServer;addrServer.sin_family = AF_INET;addrServer.sin_port = ::htons(9999);addrServer.sin_addr.S_un.S_addr = ::inet_addr("127.0.0.1");::bind(s, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));//3)接收消息while (true) {sockaddr_in addr;int addr_len = sizeof(SOCKADDR);char szBuf[1024] = { 0 };int ret = recvfrom(s, szBuf, 1024, 0, (sockaddr*)&addr, &addr_len);if (ret > 0) {szBuf[ret] = '\0';printf("%s\n", szBuf);}//防止CPU空转Sleep(10);}//4)关闭closesocket(s);return 0;
}

客户端(只要记住三步):

1)创建套接字(socket)

2)接收,发送消息(recv | send)

3)关闭(close)

#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")int main()
{//检测版本号WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD(1, 1);::WSAStartup(wVersionRequested, &wsaData);//1)创建套接字SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);//2)发送消息SOCKADDR_IN addrServer;int addr_len = sizeof(SOCKADDR);addrServer.sin_family = AF_INET;addrServer.sin_port = ::htons(9999);addrServer.sin_addr.S_un.S_addr = ::inet_addr("127.0.0.1");sendto(s, "client", 6, 0, (sockaddr*)&addrServer, addr_len);//3)关闭closesocket(s);return 0;
}

上述就是UDP连接的相关代码,为了便于理解,有些异常情况没有判断,在实战项目中,注意点就行。下面说一下TCP和UDP链接的特点,网同僚在以后开发中自己进行选取。

TCP链接:需要建立连接,稳定(超时重传机制),数据不容易丢失,流式数据;

UDP链接:无需建立链接,不稳定,数据可能会丢,报文数据

套接字属性?

其实套接字本身是还有很多属性的,如果仅仅以为掌握上述的select代码就算掌握的话,那你可就太天真了。上述本人给的select模式代码,默认是非阻塞的;其实也可以把select设置成超时阻塞模式,这就需要知道套接字属性字段了。下面我例举部分属性字段,主要目的是让知道套接字有属性就行,具体哪些可以网上查阅。

//下面是winsocket的属性,主要是命名不一样,Linux下也有,可能是别的命名方式
FIONBIO //阻塞模式
SO_LINGER //linger算法(节约流量,不知道的网上科普)
SO_SNDBUF //协议栈(这个概念不是很清楚的,先放一放,或者去科普,因为随着你写的越多,你就慢慢明白了)的发送缓冲区大小
SO_RCVBUF //协议栈的接收缓冲区大小
SO_SNDTIMEO //发送超时
SO_RCVTIMEO //接收超时
SO_REUSEADDR //重用地址
TCP_NODELAY //延时发送(如果开启,就可能会产生黏包)
//等等,还有很多字段,但是常用(本人常用)的就这么几个

到这里,关于套接字,TCP和UDP操作,基本差不多了,好像也就只这些。所以,不用把网络编程想的太困难。最后,附上本人套接字的封装代码,有需要的自行下载。本人只实现了C/C++语言(Windows/Linux)和PHP语言两个版本,其实都差不多,懂了原理各种语言都一样,都是API的名字不一样罢了。好吧,不说了,祝早日成功。

链接:https://pan.baidu.com/s/1IbZBsQxR2yUSs06aK5AmXw
提取码:jzt1

网络程序之TCP、UDP篇(其一)相关推荐

  1. Visual C#.Net网络程序开发-Tcp篇(1)

    Visual C#.Net 网络程序开发-Socket篇 Visual C#.Net网络程序开发-Tcp篇(1) Visual C#.Net网络程序开发-Tcp篇(2) Visual C#.Net网络 ...

  2. 21天学会Java之(Java SE第十三篇):网络编程、TCP/UDP通信

    如今,计算机已经成为人们学习.工作.生活必不可少的工具.人们利用计算机可以和亲朋好友在网上聊天,玩网游或发邮件等,这些功能的实现都离不开计算机网络.计算机网络实现了不同计算机之间的通信,而这些必须依靠 ...

  3. [网络] SOCKET, TCP/UDP, HTTP, FTP

    (一)TCP/UDP,SOCKET,HTTP,FTP简析 TCP/IP是个协议组,可分为三个层次:网络层.传输层和应用层: 网络层:IP协议.ICMP协议.ARP协议.RARP协议和BOOTP协议 传 ...

  4. 简单的网络协议:TCP/UDP HTTP/HTTPS

    TCP TCP传输控制协议,是一种提供可靠数据传输的通用协议 手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端机通过无线网络建立TCP连接.TCP协议可以对上层网络提供接口,使 ...

  5. 网络原理:TCP/UDP

    目录 一.数据组织格式 1.1 xml 1.2 json 1.3 protobuffer 二.传输层重要协议---UDP协议 2.1 UDP协议端格式 2.2 校验和 三.传输层重要协议---TCP协 ...

  6. 网络原理之TCP/UDP协议

    UDP协议 UDP协议端格式 16位UDP长度, 表示整个数据报(UDP首部+UDP数据)的最大长度; 如果校验和出错, 就会直接丢弃 UDP的特点 UDP传输的过程类似于寄信. 无连接: 知道对端的 ...

  7. 网络编程及TCP/UDP协议

    网络编程 1.1.概述 地球村:你在西安,一个在美国 打电话--连接--接了--通话 TCP 发短信--发完了就完事了--接收 UDP(丢包) 计算机网络: 计算机网络系统就是利用通信设备和线路将地理 ...

  8. 网络原理之TCP/UDP IP

    1 TCP/IP协议五元组:源ip+源端口号+目的ip+目的端口+协议号 windows 查看某个端口: netstat -ano|findstr "想要查看的端口号".会显示某个 ...

  9. 网络基础知识 TCP UDP IP

    文章目录 一.简介TCP/IP协议 二.传输层 2.1 UDP 2.2 TCP 三.小结 一.简介TCP/IP协议 1.简介 TCP/IP是一组协议的代名词,它包括了许多承载在IP或者TCP之间或之上 ...

最新文章

  1. Tesla Model汽车架构与FSD供应链
  2. Kubernetes StatefulSet源码分析
  3. k8s 查看ip地址属于哪个pod_Kubernetes Pod 如何获取 IP 地址
  4. HighChart模拟点击series的name显示隐藏
  5. 模拟退火算法(TSP问题)
  6. 【C语言】通过原子操作实现加减乘除功能Ⅰ
  7. 学生时代的神操作,你了解吗?
  8. 数据结构上机实践第14周项目1(2) - 验证算法(分块查找)
  9. 升级VS2019后调试出现“表达式计算器中发生内部错误”
  10. jsp三大指令(总结)
  11. Multisim 14.0安装包+详细安装步骤
  12. winform安装包签名
  13. MacOS上禁用自动启动Adobe Creative Cloud
  14. 2021-02-01 25 个常用 Matplotlib 图的 Python 代码
  15. docker-compose文件内容见下文,报错信息:redis    | Error execut
  16. C语言 数据结构 栈的线性实现 基本操作代码
  17. 零基础学前端系列教程 | 和前端谈恋爱的第005天——约会账单
  18. EChart饼图文字大小调整
  19. Android8.0 WIFI ap Tethering 相关知识
  20. vue使用element-ui 实现多套自定义主题快速切换

热门文章

  1. 内网穿透之pierced
  2. 怎么检测云服务器出口带宽,如何查看云服务器的带宽速度呢?
  3. 营长来香港啦! 9位顶尖开发者、两大香港高校、现场编程, 共商区块链金融技术与落地!...
  4. c语言编写的贪吃蛇加空战游戏加俄罗斯方块(新手)
  5. java 与操作系统底层交互_java程序员需要知道的底层知识(四)
  6. Mac常见问题:解决系统自带的中文输入法不显示预选中文的问题!
  7. 让桌面显示windows 10 系统信息
  8. 2018 ACM-ICPC 宁夏邀请赛线下赛
  9. 6-5 从键盘读入一串字符后去除首尾字符后的字符串按降序排序 (10 分)请编写函数Sort函数,将字符串中除首、尾字符外的其余字符按降序排列。函数接口定义:void Sort( char *
  10. jxl.write.biff.RowsExceededException: The maximum number of rows permitted on a worksheet been exce