C++网络编程
TCP/IP协议
网络介质层:将模拟信号转化成数字信号,会形成一个MAC地址(本机地址,一般情况下不会变化)。百兆宽带用4根线,千兆宽带用8根线进行传输。
网络层:进行网络层的通讯,IP地址对IP地址。
数据进行协议栈时的封装:
WireShark抓包工具
可以通过wireshark进行网络抓包分析,具体教程可以参考网络分析工具——WireShark的使用(超详细)教程进行软件安装和使用,注意Windows可能需要安装一个小插件,软件才能实现本地化配置,具体博客内已经详细说明了情况,这里附上软件下载地址:win10pcap。
windows和linux系统之间配置共享
使用windows编辑工具直接编辑linux代码:
- 直接用在windows中提交linux上源码
- 使用VS直接编辑linux跨平台代码
- 安装配置samba
samba安装
切换到管理员账号/使用sudo权限,
apt-get install sambda
然后编辑sambda的配置文件
vim /etc/samba/smb.conf
在文件末尾加载以下内容:
[code] path=/your_dir writeable=yes browseable=yes guest ok =yes
然后使用命令在服务器上创建your_dir:
mkdir /your_dir
然后重启smbd,先杀掉进程,在重启:
pkill smbd smbd
接着设置your_dir的访问权限,防止不能在文件内进行操作:
chown nobody:nogroup /your_dir
然后使用cmd,输入\\ip_adress,我的就是\\192.168.20.121回车之后就可以跳转到文件夹中,进行创建文件、修改文件,修改后服务器也会自动进行修改。
socke函数
socket又称为套接字。
套接字的定义:
1.套接字是一个主机本地应用程序所创建的,为操作系统所控制的接口(门),其实也就是程序使用socket通知操作系统对主机硬件进行操作。
2.应用进程通过这个接口,使用传输层提供的服务,跨网络发送(/接收)消息到(/从)其他应用进程。
3.Client/server模式的通信接口——套接字接口。
socket其实就是文件,Socket发送方和接收方都不是实时的,需要排队进行处理(好处是稳定可靠、坏处是可能不执行)。socket不仅仅只用于网络通信,还可以用于蓝牙、红外等。
windows/linux创建socket
//使用ifdef-endif可以在程序运行开始前识别主机环境 #ifdef WIN32 #include<Windows.h> #else #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> //函数名替换,重定义,将close关闭sock的函数转化为closesocket #define closesocket close #endif #include <iostream> using namespace std; int main(int argc,char* argv[]) { #ifdef WIN32 //通过进程启动Winsock DLL使用 WSADATA ws; WSAStartup(MAKEWORD(2, 2), &ws); #endif for (int i = 0; i < 1000; i++) { //创建socket,创建失败返回-1,AF_INET表示ipv4协议,SOCK_STREAM表示接受tcp/ip协议的数据 int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { cout << "create socket failed" << endl; return -1; } cout << "[" << sock << "]" << endl; //socket关闭 closesocket(sock); } return 0; }
windows用户在vs studio上运行需要添加ws2_32.lib进行编译,否则会出错。具体可以参考Visual Studio 2019 C++实现socket通信,添加ws2_32.lib库,新手代码。
TCP协议
TCP协议的性质:
- TCP是面向连接的,需要三次握手确定连接后才开始发送数据,而UDP是直接发送数据的。
- TCP提供了可靠性,实现了丢失重传。RTT的估算。
- TCP通过给所发送数据的每一个段管理一个序列号进行排序。
- TCP提供流量控制。通信窗口、拥塞窗口。
- TCP连接是全双工的,也就是说基于TCP连接的程序/进程可以同时接收数据和发送数据,具有不同的传出信道。
TCPServer在windows/linux通用程序
#ifdef WIN32 #include<Windows.h> #define socklen_t int #else #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<arpa/inet.h> //函数名替换,重定义,将close关闭sock的函数转化为closesocket #define closesocket close #endif #include <iostream> #include<stdlib.h> #include<string.h> #include<thread> using namespace std; class TCPThread { public: void main() { while (true) { //recv和send都不能保证能够发送信息和收到信息成功 char buf[1024] = { 0 }; //接收客户端发送得请求,并将其内容存储在buf字符串中 int reclen = recv(client_sock, buf, sizeof(buf) - 1, 0); if (reclen <= 0) { break; } buf[reclen] = '\0'; if (strstr(buf, "quit") != NULL) { char re[] = "quit success!\n"; //服务器向客户端发送数据 send(client_sock, re, strlen(re) + 1, 0); break; } int sendlen = send(client_sock, "ok\n", 4, 0); cout << "receive data: " << buf << endl; } closesocket(client_sock); delete this; } int client_sock=0; }; int main(int argc, char* argv[]) { #ifdef WIN32 //通过进程启动Winsock DLL使用 WSADATA ws; WSAStartup(MAKEWORD(2, 2), &ws); #endif //创建socket,创建失败返回-1,AF_INET表示ipv4协议,SOCK_STREAM表示接受tcp/ip协议的数据 int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { cout << "create socket failed" << endl; return -1; } cout << "[" << sock << "]" << endl; //获得端口号 unsigned short port = 8080; if (argc > 1) { port = atoi(argv[1]); } //绑定地址 sockaddr_in saddr; saddr.sin_family = AF_INET; //大端字节序和小端字节序的问题, saddr.sin_port = htons(port); //0设置为绑定本机地址 saddr.sin_addr.s_addr = htonl(0); //绑定地址 if (bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0) { cout << "bind port " << port << " failed.\n"; return -2; } cout << "bind port " << port << " successful.\n"; //监听客户端发送的信息 //backlog=10表示缓冲大小 listen(sock, 10); while (true) { //每个连接就会生成一个client sockaddr_in caddr; socklen_t len = sizeof(caddr); //在accept之前会进行三次握手(由操作系统完成),accept只是获取了握手后的信息 int client_sock = accept(sock, (sockaddr*)&caddr, &len); if (client_sock <= 0) { break; } cout << "accept client " << client_sock << ".\n"; char* ip = inet_ntoa(caddr.sin_addr); unsigned short cport = ntohs(caddr.sin_port); cout << "client ip address " << ip << ".\n"; cout << "client port " << cport << ".\n"; TCPThread* th = new TCPThread(); th->client_sock = client_sock; //启动多线程,第一个参数是函数地址,第二个为对象本身 thread sth(&TCPThread::main, th); //释放主线程中子线程的资源 sth.detach(); } //socket关闭 closesocket(sock); return 0; }
TCP封装成函数便于使用
XTCP.h
#ifndef XTCP_H //保证只初始化一次 #define XTCP_H #ifdef WIN32 #pragma once #ifdef XSOCKET_EXPORTS #define XSOCKET_API __declspec(dllexport) #else #define XSOCKET_API __declspec(dllimport) #endif #else #define XSOCKET_API #endif // WIN32 #include<string> class XSOCKET_API XTCP { public: int createSocket(); bool bindListen(unsigned short port); void closeSocket(); int receiveData(char* buf,int bufsize); int sendData(const char* buf, int sendsize); bool connectSocket(const char* ip, unsigned short port); XTCP acceptClient(); XTCP(); virtual ~XTCP(); int sock = 0; unsigned short port = 0; char ip[16]; }; #endif // !XTCP_H
XTCP.cpp
#include "XTCP.h" #ifdef WIN32 #include<Windows.h> #define socklen_t int #else #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<arpa/inet.h> //函数名替换,重定义,将close关闭sock的函数转化为closesocket #define closesocket close #define strcpy_s strcpy #endif #include <iostream> #include<stdlib.h> #include<cstring> XTCP::XTCP() { #ifdef WIN32 static bool is_first = true; if (is_first) { is_first = false; //通过进程启动Winsock DLL使用 WSADATA ws; WSAStartup(MAKEWORD(2, 2), &ws); } #endif } bool XTCP::connectSocket(const char* ip, unsigned short port) { if (sock <= 0) { createSocket(); } sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); //将字符串ip地址转化为网络地址 saddr.sin_addr.s_addr = inet_addr(ip); if (connect(sock, (const sockaddr*)&saddr, sizeof(saddr)) != 0) { // strerror(errno)将错误转化为字符串 std::cout << "connect " << ip << " : " << port << " failed! "; return false; } std::cout << "connect " << ip << " : " << port << " success!\n "; return true; } int XTCP::createSocket() { //创建socket,创建失败返回-1,AF_INET表示ipv4协议,SOCK_STREAM表示接受tcp/ip协议的数据 sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { std::cout << "create socket failed" << std::endl; } return sock; } bool XTCP::bindListen(unsigned short port) { if (sock <= 0) { createSocket(); } //绑定地址 sockaddr_in saddr; saddr.sin_family = AF_INET; //大端字节序和小端字节序的问题, saddr.sin_port = htons(port); //0设置为绑定本机地址 saddr.sin_addr.s_addr = htonl(0); //绑定地址 if (bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0) { std::cout << "bind port " << port << " failed.\n"; return false; } std::cout << "bind port " << port << " successful.\n"; //监听客户端发送的信息 //backlog=10表示缓冲大小 listen(sock, 10); return true; } XTCP XTCP::acceptClient() { XTCP tcp; //每个连接就会生成一个client sockaddr_in caddr; socklen_t len = sizeof(caddr); //在accept之前会进行三次握手(由操作系统完成),accept只是获取了握手后的信息 int client_sock = accept(sock, (sockaddr*)&caddr, &len); if (client_sock <= 0) { return tcp; } tcp.sock = client_sock; std::cout << "accept client " << client_sock << ".\n"; char* ip = inet_ntoa(caddr.sin_addr); strcpy_s(tcp.ip, ip); tcp.port = ntohs(caddr.sin_port); std::cout << "client ip address " << tcp.ip << ".\n"; std::cout << "client port " << tcp.port << ".\n"; return tcp; } int XTCP::receiveData(char* buf, int bufsize) { return recv(sock, buf, bufsize, 0); } int XTCP::sendData(const char* buf, int sendsize) { //需要全部发送完全才能结束 int sendedSize = 0; while (sendedSize!=sendsize) { int len = send(sock, buf + sendedSize, sendsize - sendedSize, 0); if (len <= 0) { break; } sendedSize += len; } return sendedSize; } void XTCP::closeSocket() { if (sock <= 0) { return; } closesocket(sock); } XTCP::~XTCP() { }
通过上述函数就可以实现在Windows封装成lib文件,在Linux封装成so文件进行更方便的使用。更多细节参考C/C++封装:Windows/Linux下封装.lib/.so文件。
makefile最基本使用
tcpserver:testSocket2.cpp XTCP.h XTCP.cpp g++ testSocket2.cpp XTCP.cpp -o tcpserver -std=c++11 -lpthread 目标文件:源文件和头文件(空格隔开) g++ 源文件(不需要头文件) -o 目标文件名 附加命令 -lpthread:表示多线程使用 -std:表示c++版本
直接使用make命令就可以执行该程序了
编译成.so文件:
libxsocket.so:XTCP.cpp XTCP.h g++ $+ -o $@ -fpic -shared -std=c++11 $+:文件列表——依赖项 $@:目标文件——目标项 -fpic:代码与位置无关 -shared:把代码编译成动态链接库
三次握手
第一次握手:客户端给服务器发送一个J信号。
第二次握手:服务器接收客户端发送的请求,并得到客户端发送的J信号(但这个信号不一定正确),所以服务器会返回J+1信号和一个K信号。
第三次握手:客户端接收服务器发送返回的J+1信号,如果正确表示客户端和服务器端通信正常,但是此时服务器端还不知道通信是否正常,所以客户端还需要将服务器发送的K进行处理,发送K+1信号给服务器端,告知服务器通信正常。
服务器和客户端进行TCP/IP通信的流程
服务器的close会关闭两个通信口,一个是接收数据通信的send和recv的通道,一个是socket客户端和服务器端连接的通道。
设置阻塞方式
在Windows和Linux中,socket阻塞和及时通信方式具有很大的差异,windows通过使用ioctsocket()函数进行设置,而linux则是通过fcntl()函数进行处理。具体如下代码所示。
bool XTCP::setBlock(bool isBlock) { //默认为阻塞模式 isBlock=true if (sock <= 0) { return false; } #ifdef WIN32 unsigned long ul = 0; if (!isBlock) { ul = 1; } //ul=1就会立刻返回结果,反之,则不会立即返回,只有windows才有这个函数 ioctlsocket(sock, FIONBIO, &ul); #else #include<fctnl.h> //头文件 //fcntl操作文件描述符的函数, int flag=fcntl(sock, F_GETFL, 0); if (flag < 0) { return false; } if (isBlock) { flag = flag & ~O_NONBLOCK; } else { flag = flag | O_NONBLOCK; } if (fcntl(sock, F_SETFL, flag) != 0) { return false; } #endif // WIN32 return true; }
select实现connect超时退出
当客户端请求ip、port出现错误时,connect()原始来阻塞模式,会导致程序一直阻塞在这里,无法执行后续代码。而select()函数就可以打破这个僵局,实现超时跳过的操作,并且具有多路复用、同时监听多个文件的功能,具体代码可以如下所示:
bool XTCP::connectSocket(const char* ip, unsigned short port, int timeoutms) { if (sock <= 0) { createSocket(); } sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); //将字符串ip地址转化为网络地址 saddr.sin_addr.s_addr = inet_addr(ip); setBlock(false); fd_set set; if (connect(sock, (const sockaddr*)&saddr, sizeof(saddr)) != 0) { //置空 FD_ZERO(&set); FD_SET(sock, &set); // 两个参数的结构体,分别是秒和微秒 timeval tm; tm.tv_sec = 0; tm.tv_usec = timeoutms * 1000; if (select(sock + 1, 0, &set, 0, &tm) <= 0) { printf("connect time or error!\n"); // strerror(errno)将错误转化为字符串 std::cout << "connect " << ip << " : " << port << " failed! "; return false; } } setBlock(true); std::cout << "connect " << ip << " : " << port << " success!\n "; return true; }
更多select()函数解析可以查看网络编程之select。
apache2使用
apache用于网络测数,linux安装apt-get install apache2,安装完成后就可以使用命令进行操作了。例如:
ab -n 1000 -c 5 https://www.baidu.com/
表示连接数为1000,连接的线程数为5,访问的网址为:https://www.baidu.com/,也可以测自己本地的局域网服务器。输出结果如下图所示:
root@xiehou--ubuntu:/cpp_study/socket/src/tcpClient# ab -n 100 -c 5 https://www.baidu.com/ This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking www.baidu.com (be patient).....done Server Software: BWS/1.1 Server Hostname: www.baidu.com Server Port: 443 SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128 Server Temp Key: ECDH P-256 256 bits TLS Server Name: www.baidu.com Document Path: / Document Length: 227 bytes Concurrency Level: 5 Time taken for tests: 1.305 seconds Complete requests: 100 Failed requests: 0 Total transferred: 139893 bytes HTML transferred: 22700 bytes Requests per second: 76.65 [#/sec] (mean) Time per request: 65.231 [ms] (mean) Time per request: 13.046 [ms] (mean, across all concurrent requests) Transfer rate: 104.72 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 36 47 15.2 44 125 Processing: 11 15 2.6 14 33 Waiting: 11 14 2.6 14 32 Total: 49 62 15.6 58 139 Percentage of the requests served within a certain time (ms) 50% 58 66% 60 75% 62 80% 64 90% 73 95% 109 98% 135 99% 139 100% 139 (longest request)
epoll多路复用IO高并发(Linux中)
epoll应对的现状:
1.大量并发连接中只有少量活跃。
2.LT(level Triggered),水平触发, 一直触发。
3.ET(Edge Triggered),边沿触发,只触发一次 , 只有文件描述符从不可读变为可读的时候才会被触发, EPOLLET 这个宏用于设置ET模式。
更多epoll学习资料可以参考tcp使用epoll进行实现并发。
HTTP协议
- HTTP超文本传输协议,所有的WWW文件都必须遵守这个标准。
- HTTP/1.0短连接(接受连接之后就会关闭),HTTP/1.1它支持持续连接,端口默认是80端口。
HTTP URL(URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息)格式如http://host[":" port][abs_path]。
http表示要通过HTTP协议来定位网络资源,host表示合法的Internet主机域名或者IP地址;port指定一个端口号,为空则使用缺省端口80;abs_path指定请求资源的URI;如果URL中没有给出abs_path,那么当它作为请求URI时,必须以“/”的形式给出,通常这个工作浏览器自动完成。
Method Request-URI HTTP-Version CRLF,CRLF表示换行。
HTTP请求的方法具有很多种:
- GET。请求获取Request-URI所标识的资源。
- POST,在Request-URI所标识的资源后附加新的数据。
- HEAD。请求获取由Request-URI所表示的资源的响应消息报头。
- PUT。请求服务器存储一个资源,并用Request-URI作为标识。
- DELETE。请求服务器删除Request-URI所标识的资源。
- TRACE。请求服务器会送收到的请求消息,主要用于测试或诊断
- CONNECT。保留将来使用。
- OPTIONS。请求查询服务器的性能,或者查询与资源相关的选项和需求。
HTTP响应信息包含:
主要有三个部分组成,分别是:状态行、消息报头、响应正文。
状态行:HTTP-Version Status-Code Reason-Phrase CRLF。HTTP-Version表示服务器HTTP协议版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。
使用socket简单封装一个http服务器
XTCP.h
#ifndef XTCP_H //保证只初始化一次 #define XTCP_H #ifdef WIN32 #pragma once #ifdef XSOCKET_EXPORTS #define XSOCKET_API __declspec(dllexport) #else #define XSOCKET_API __declspec(dllimport) #endif #else #define XSOCKET_API #endif // WIN32 #include<string> class XSOCKET_API XTCP { public: int createSocket(); bool bindListen(unsigned short port); void closeSocket(); int receiveData(char* buf,int bufsize); int sendData(const char* buf, int sendsize); bool connectSocket(const char* ip, unsigned short port); bool connectSocket(const char* ip, unsigned short port,int timeoutms); bool setBlock(bool isBlock); XTCP acceptClient(); XTCP(); virtual ~XTCP(); int sock = 0; unsigned short port = 0; char ip[16]; }; #endif // !XTCP_H
XTCP.cpp
#include "XTCP.h" #ifdef WIN32 #include<Windows.h> #define socklen_t int #else #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<arpa/inet.h> #include<fcntl.h> //函数名替换,重定义,将close关闭sock的函数转化为closesocket #define closesocket close #define strcpy_s strcpy #endif #include <iostream> #include<stdlib.h> #include<cstring> XTCP::XTCP() { #ifdef WIN32 static bool is_first = true; if (is_first) { is_first = false; //通过进程启动Winsock DLL使用 WSADATA ws; WSAStartup(MAKEWORD(2, 2), &ws); } #endif } bool XTCP::connectSocket(const char* ip, unsigned short port, int timeoutms) { if (sock <= 0) { createSocket(); } sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); //将字符串ip地址转化为网络地址 saddr.sin_addr.s_addr = inet_addr(ip); setBlock(false); fd_set set; if (connect(sock, (const sockaddr*)&saddr, sizeof(saddr)) != 0) { //置空 FD_ZERO(&set); FD_SET(sock, &set); // 两个参数的结构体,分别是秒和微秒 timeval tm; tm.tv_sec = 0; tm.tv_usec = timeoutms * 1000; if (select(sock + 1, 0, &set, 0, &tm) <= 0) { printf("connect time or error!\n"); // strerror(errno)将错误转化为字符串 std::cout << "connect " << ip << " : " << port << " failed! "; return false; } } setBlock(true); std::cout << "connect " << ip << " : " << port << " success!\n "; return true; } bool XTCP::connectSocket(const char* ip, unsigned short port) { if (sock <= 0) { createSocket(); } sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); //将字符串ip地址转化为网络地址 saddr.sin_addr.s_addr = inet_addr(ip); if (connect(sock, (const sockaddr*)&saddr, sizeof(saddr)) != 0) { // strerror(errno)将错误转化为字符串 std::cout << "connect " << ip << " : " << port << " failed! "; return false; } std::cout << "connect " << ip << " : " << port << " success!\n "; return true; } int XTCP::createSocket() { //创建socket,创建失败返回-1,AF_INET表示ipv4协议,SOCK_STREAM表示接受tcp/ip协议的数据 sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { std::cout << "create socket failed" << std::endl; } return sock; } bool XTCP::bindListen(unsigned short port) { if (sock <= 0) { createSocket(); } //绑定地址 sockaddr_in saddr; saddr.sin_family = AF_INET; //大端字节序和小端字节序的问题, saddr.sin_port = htons(port); //0设置为绑定本机地址 saddr.sin_addr.s_addr = htonl(0); //绑定地址 if (bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0) { std::cout << "bind port " << port << " failed.\n"; return false; } std::cout << "bind port " << port << " successful.\n"; //监听客户端发送的信息 //backlog=10表示缓冲大小 listen(sock, 10); return true; } XTCP XTCP::acceptClient() { XTCP tcp; //每个连接就会生成一个client sockaddr_in caddr; socklen_t len = sizeof(caddr); //在accept之前会进行三次握手(由操作系统完成),accept只是获取了握手后的信息 int client_sock = accept(sock, (sockaddr*)&caddr, &len); if (client_sock <= 0) { return tcp; } tcp.sock = client_sock; std::cout << "accept client " << client_sock << ".\n"; char* ip = inet_ntoa(caddr.sin_addr); strcpy_s(tcp.ip, ip); tcp.port = ntohs(caddr.sin_port); std::cout << "client ip address " << tcp.ip << ".\n"; std::cout << "client port " << tcp.port << ".\n"; return tcp; } int XTCP::receiveData(char* buf, int bufsize) { return recv(sock, buf, bufsize, 0); } bool XTCP::setBlock(bool isBlock) { //默认为阻塞模式 if (sock <= 0) { return false; } #ifdef WIN32 unsigned long ul = 0; if (!isBlock) { ul = 1; } //ul=1就会立刻返回结果,反之,则不会立即返回,只有windows才有这个函数 ioctlsocket(sock, FIONBIO, &ul); #else //fcntl操作文件描述符的函数, int flag=fcntl(sock, F_GETFL, 0); if (flag < 0) { return false; } if (isBlock) { flag = flag & ~O_NONBLOCK; } else { flag = flag | O_NONBLOCK; } if (fcntl(sock, F_SETFL, flag) != 0) { return false; } #endif // WIN32 return true; } int XTCP::sendData(const char* buf, int sendsize) { //需要全部发送完全才能结束 int sendedSize = 0; while (sendedSize!=sendsize) { //printf("--sock:%d--\n", sock); int len = send(sock, buf + sendedSize, sendsize - sendedSize, 0); if (len <= 0) { break; } sendedSize += len; } return sendedSize; } void XTCP::closeSocket() { if (sock <= 0) { return; } closesocket(sock); } XTCP::~XTCP() { }
httpServer.cpp
#include"XTCP.h" #include<iostream> #include<thread> #include<cstring> #include<string> using namespace std; class HTTPThread { public: void main() { char buf[10000] = { 0 }; //接收http客户端请求 int recvLen = client.receiveData(buf, sizeof(buf) - 1); if (recvLen <= 0) { client.closeSocket(); delete this; return; } printf("===========recv=============== \n%s========================\n", buf); //回应http请求 string rmsg = ""; rmsg = "HTTP/1.1 200 OK\r\n"; rmsg += "Server: XHttp\r\n"; rmsg += "Content-Type: text/html\r\n"; rmsg += "Content-Length: "; rmsg += "10\r\n"; rmsg += "\r\n"; rmsg += "0123456789"; int flag=client.sendData(rmsg.c_str(), rmsg.size()); if (flag > 0) { cout << "=============send=====================" << endl; cout << rmsg << endl; cout << "======================================" << endl; } client.closeSocket(); //while (true) { // //recv和send都不能保证能够发送信息和收到信息成功 // char buf[1024] = { 0 }; // //接收客户端发送得请求,并将其内容存储在buf字符串中 // int reclen = client.receiveData(buf, sizeof(buf) - 1); // if (reclen <= 0) { // break; // } // buf[reclen] = '\0'; // if (strstr(buf, "quit") != NULL) { // char re[] = "quit success!\n"; // //服务器向客户端发送数据 // client.sendData(re, strlen(re) + 1); // break; // } // int sendlen = client.sendData("ok\n", 4); // cout << "receive data: " << buf << endl; //} //client.closeSocket(); delete this; } XTCP client; }; int main(int argc, char* argv[]) { //获得端口号 unsigned short port = 80; if (argc > 1) { port = atoi(argv[1]); } XTCP server; server.createSocket(); server.bindListen(port); while (true) { //每个连接就会生成一个client XTCP client = server.acceptClient(); HTTPThread* th = new HTTPThread(); th->client = client; //启动多线程,第一个参数是函数地址,第二个为对象本身 thread sth(&HTTPThread::main, th); //释放主线程中子线程的资源 sth.detach(); } //socket关闭 server.closeSocket(); return 0; }
UDP协议
UDP:用户数据报协议,具有特性
- UDP提供无连接服务。
- UDP缺乏可靠性支持,应用程序必须实现:确认、超时、重传、流控等。
- UDP面向记录服务。
UDP的数据报格式:
Windows配置C++的UDP协议:
在VS中 项目属性 -> 连接器 -> 输入 -> 附加依赖项添加ws2_32.lib。
没有配置则会显示socket相关的函数没有找到。
UDP通信Windows版本
udpserver.cpp
#include<Windows.h> #include <iostream> using namespace std; int main(int argc,char* argv[]) { unsigned short port = 8080; if (argc > 1) { port = atoi(argv[1]); } WSADATA ws; WSAStartup(MAKEWORD(2, 2), &ws); int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock <= 0) { cout << "create socket failed!" << endl; return -1; } sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); saddr.sin_addr.s_addr = htonl(0); if (bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0) { cout << "bind port " << port << " failed!" << endl; return -2; } cout << "bind port " << port << "success!" << endl; listen(sock, 10); sockaddr_in client; int len = sizeof(client); char buf[10240] = { 0 }; int re = recvfrom(sock, buf, sizeof(buf), 0, (sockaddr*)&client, &len); if (re <= 0) { cout << "recefrom failed!" << endl; return -3; } buf[re] = '\0'; cout << buf << endl; return 0; }
udpclient.cpp
#include<Windows.h> #include <iostream> using namespace std; int main(int argc, char* argv[]) { unsigned short port = 8080; if (argc > 1) { port = atoi(argv[1]); } WSADATA ws; WSAStartup(MAKEWORD(2, 2), &ws); int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock <= 0) { cout << "create socket failed!" << endl; return -1; } sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //htonl(0); int len=sendto(sock, "12345", 6, 0,(sockaddr*)&saddr, sizeof(saddr)); cout << "sendto size is " << len << endl; return 0; }
UDP客户端和服务器端通信成功!!!
UDP广播
广播所用的广播地址为255.255.255.255。
本地广播信息是不会被路由器转发。