网络服务器编程

网络服务器编程


1概述

服务器编程

主要内容:TCP / 服务器 / 网络协议
虽然UDP看起来简单,容易实现,但在绝大多数情况 下都应该使用TCP
现在UDP几乎没有使用场景!
(原因:UDP只适用于局域网,不能穿越路由)

什么叫服务器?
服务器,就是提供服务的软件,它是一个程序。

常用的:
WEB服务器: 提供网页文件下载服务
视频服务器: 提供视频流服务
游戏服务器: 提供游戏服务

专业视角:
HTTP服务器: tomcat, apache, nginx …
数据库服务器: MySQL, oracle, SQL server…
FTP, SAMBA, NFS, …

服务器编程模型
TcpClient
TcpClient
TcpClient
Tcp Server
1对多: 多个客户端可以同时连接一个服务器
192.168.1.100
192.168.1.101
192.168.1.102
192.168.1.120
服务器编程模型
请求应答模型: 请求 -> 应答
Client Server
请求
应答
请求
应答
服务器不会主动找客户端。。。
演示
打开流览器, 输入
http://afanihao.cn/test.html Wireshark ,
host afanihao.cn
浏览器 网站
请求 /test.html
应答: 网页内容

2写一个HTTP客户端

服务器编程
三个要素:客户端,服务器,通讯协议
客户端 服务器
通讯协议
举例
以网页下载为例, 在浏览器里输入
http://afanihao.cn/test.html

客户端:浏览器程序
服务器:tomcat程序
协议:HTTP

此时:服务器 + 通讯协议 都有了,我们可以只 写一个客户端程序。。

写一个HTTP客户端
1 确定服务器的IP
Ping afanihao.cn
2 创建TCP socket,连接到服务器
3 发送请求 ,请求的格式要符合HTTP协议
4 接收应答

什么是HTTP协议?
请求
GET /test.html HTTP/1.1
应答
HTTP/1.1 200 OK
Content-Length: 293
\r\n
xxxxxxxxxxxxxx

小结
初步介绍 客户端、服务器、通讯协议 这三要素

3一般的TCP服务器编程框架

TCP服务器的一般框架
首先定好端口号,关闭防火墙 Port = 8888
1 socket.open(port)
2 socket.listen()
3 while( )
{
p = socket.accept() 前台接待,来了一个客户
task = new TcpServiceTask§ 创建一个服务线程 TcpTaskMonitor.i()->monitor(task); 纳入回收管理
}

服务线程 TcpServiceTask
一般框架

1 接收请求
2 根据请求做出应答
3 下一个请求
4 再做应答

结束

要点
理解TcpServer的工作模式
1 主socket相当于前台接待
主socket不负责收发消息

2 有client连接上来时,指派一个业务员 § 由p与客户端socket形成一个通信链路
每个线程为一个client服务

服务线程的回收
每个client再关闭,相应的ServiceTask应该被销毁
建立一个线程管理器专门来管理ServiceTask

// 将task将给Monitor管理
TcpTaskMonitor::i()->monitor(task)

扩展思考
如果一个client连上来,什么也不发,占着一个 线程不放,怎么办?
如果有人恶意建立1000个socket连上来,一直 保持连接,那我们的服务器不是都被占着了吗?

4网络抓包与分析方法

部署服务器软件
1 找一个主机
办公室电脑,Vmware虚拟机, 云上的虚拟机

2 Server程序静态编译 /MTd, ,拷贝exe过去
(参考补充篇 之 动态库)

3 确认端口未被占用
netstat -ano | findstr"8888"

4 确认关闭防火墙

5 运行服务器软件

Wireshark网络抓包
网络抓包是网络编程调试的有效手段

1 可以确定数据格式是否符合协议的规定
2 可以确定是客户端的错,还是服务器的错
过滤器:
host 123.57.248.214 and port 8888

抓包分析
Wireshark的功能

(1)抓取网卡接收/发送的数据包
(2)按协议进行解析 ( 内置了大量协议的解析器 )
(3) 可以把抓包数据存为文件

发送测试
1 发送字符串 ( char*)
2 发送整数 ( unsigned int )
整数如何发送? - 将一个int按大端编码为4个字节

小结
(1) 服务器软件的部署
(2) 抓包练习 , 数据分析
(3) 发送字符串与整数

5消息的封包

TCP的流式传输 UDP: 按包发送
发送方: hosta.sendTo(“ab”, hostBAddr); hosta.sendTo(“cd”, hostBAddr); hosta.sendTo(“ef”, hostBAddr); 接收方: hostb.recvFrom(buf, …) // “ab” hostb.recvFrom(buf,…) // “cd” hostb.recvFrom(buf,…) // “ef” host A hostB UDP
TCP的流式传输 TCP: 流式传输,没有边界
发送方: client.send(“ab”, …); client.send(“cd”, …); client.send(“ef”, …); 接收方: server. recv(buf, …) // ?? server. recv(buf,…) // ?? server. recv(buf,…) // ?? Client Server TCP
Send(…)
放入Send 从Recv取走
OS
取走
OS
Recv(…)
存入
Internet
TCP: 接收/发送缓冲区
必须清楚的事实
1 调用Send后,数据没有立即发出,而是存在了 发送缓冲区里 2 操作系统每次取走一定量的数据发走
3 无论你收不收,操作系统都会把数据收下来存到 缓冲区里
4 调用Recv时,只是从缓冲区里取走数据
5 缓冲区里有多少就取多少:比如,abc已经到达, def尚未到达, 则Recv就把”abc”取走

消息的封包
在TCP传输中,每条消息的边界由协议设计者自己 确定。这就是消息的封包
封包的结果,就是使接收方能清晰的知道,哪些是 第1条数据,哪些是第2条数据。。每条消息都有边 界。
消息的封包
一种常用的封包方法:
其中, type: 2字节, 表示消息类型, 如0x0104 length: 2字节, 表示数据的长度, 如0x000A data: 数据字节 例如, 01 04 00 0A 68 65 6C 6C 6F 77 6F 72 6C 64
type length data
消息的封包格式
01 04 00 0A 68 65 6C 6C 6F 77 6F 72 6C 64
头部:固定长度,4个字节 数据部分:变长,其长度由length字段指定
接收策略: 先接收头部4个字节,得到type和length 再接收length长度的数据
消息的封包
AfTcpSend: 负责封包的发送 AfTcpSend snd(sock); snd.send( type, length, data);
AfTcpRecv: 负责封包的接收 AfTcpRecv rcv(sock); rcv.recv();

小结
回顾TCP的流式传输
介绍为什么要对消息封包,如何封包

6数据的格式:JSON

数据的格式
前面已经建立起消息的收发框架 AfTcpSend: 负责发送消息 AfTcpRecv: 负责接收消息
下面的问题是:消息以什么格式发送?
有几种选择:(见补充篇 )

  • XML
  • JSON
  • 字节编码 AfByteBuffer

数据的格式
比如说,客户端发送一个登录验证请求
如果以这种格式将消息发送给服务器,那服务器怎么解 析呢?有点麻烦,尤其是复杂结构的数据。
我们选择用JSON格式传输数据。。。
type length username=shaofa&password=123456
JSON格式的数据
请求
0x0101 … {
“username”:”shaofa”, “password”:”123456”
}
… … {
“errorCode”: 0, “reason”: ”OK”
}
应答
复习: JSON的构造
本例使用的库是: jsoncpp (也可以使用jsoncons, rapidjson…)
Json::Value req; Json::FastWriter writer; req[“username”] = username; req[“password”] = password; std::string jsonreq = writer.write(req);
复习: JSON的解析
本例使用的库是: jsoncpp Json::Reader reader; Json::Value resp; if (!reader.parse(jsonresp, resp, false)) throw string(“bad json format!\n”); int errorCode = resp[“errorCode”].asInt(); string reason = resp[“reason”].asString(); if(errorCode != 0) throw string(reason);

小结
在消息封包的基础上,学会了对消息的格式 化:JSON格式传输数据
type length {
“username”:”shaofa”, “password”:”123456”

7做一个类似FTP的文件服务器

本节课目标
实现一个类似FTP的文件服务器
( FileZilla server / client)

login 登录
cd 切换目录
ls 文件列表
get 下载文件到本地
put 上传文件到服务器

服务器端
MyFtpService
{
string m_homeDir; // 根目录所在位置
string m_path; // 路径
int on_cd() {}
int on_ls() {}
virtual int service()
{
}
}

客户端
MyFtpClient
{
connect()
disconnect()
req_cd( path )
req_ls()
}

工具类与工具函数
AfCommonUtils::split ()
将输入的命令行字符串分解为argc argv

AfFileUtils::ls ()
得到一个目录下的文件/文件夹的列表

小结
login: 大家自己补上 get/put: 文件的传递后面再介绍

8文件的下载:消息模式和流模式

本节课目标
实现一个类似FTP的文件服务器 ( FileZilla server / client)
login 登录 cd 切换目录 ls 文件列表 get 下载文件到本地 put 上传文件到服务器
消息流程
Client Server
MSG_GET
ACK_DATA_CONTINUE
ACK_NORMAL
ACK_DATA_FINISH
要下载哪个文件…
第一次应答
连续发送数据
结束发送
服务器端
on_get ( ) { 接收请求 发送应答 while( true) { 发送 ACK_DATA_CONTINUE } ACK_DATA_FINISH }

客户端
req_get ( fileName, localFilePath) { 发送 请求 MSG_GET 接收 应答 while(true) { ACK_DATA_CONTINUE / ACK_DATA_FINISHED } }
消息模式 / 流模式
消息模式:
(控制命令) 一问一答,往往是文本格式 (如JSON)
不考虑效率

流模式: (文件流,视频流等大量数据的传输)
一问多答
追求效率,字节编码

两个细节 (非常重要)
1 每次发送24K数据是否太多?
保证发送缓冲区填满

2 采用一问一答模式来做大量数据传输可以吗?
不可以。效率极低。
(1)消息都不是立即发送的
(2)只有填满缓冲区,才能保证OS会以最大速率 传输。

小结
流式传输:类似有RTMP协议。

注:FTP的控制部分采用的消息方式,数据
部分采用的是流方式。

消息模式:文本格式(一问一答命令)
流模式:字节(大量数据)

学习资源 《C语言/C++学习指南》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值