C++sokcet网络编程笔记

简介: 网络介质层:将模拟信号转化成数字信号,会形成一个MAC地址(本机地址,一般情况下不会变化)。百兆宽带用4根线,千兆宽带用8根线进行传输。

C++网络编程


TCP/IP协议


2cae29e104664704bd8bf06a8a6ccd33.png


网络介质层:将模拟信号转化成数字信号,会形成一个MAC地址(本机地址,一般情况下不会变化)。百兆宽带用4根线,千兆宽带用8根线进行传输。


网络层:进行网络层的通讯,IP地址对IP地址。


数据进行协议栈时的封装:


a9231535ba624cbab571733756895bf3.png


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模式的通信接口——套接字接口。


b9e15b0f4474440fbbcef1a6f9e211fc.png


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库,新手代码


66c756e72edc4d949adaadb56bd03fbe.png


TCP协议


TCP协议的性质:


  • TCP是面向连接的,需要三次握手确定连接后才开始发送数据,而UDP是直接发送数据的。
  • TCP提供了可靠性,实现了丢失重传。RTT的估算。
  • TCP通过给所发送数据的每一个段管理一个序列号进行排序。
  • TCP提供流量控制。通信窗口、拥塞窗口。
  • TCP连接是全双工的,也就是说基于TCP连接的程序/进程可以同时接收数据和发送数据,具有不同的传出信道。


091a792497b9419f9b6cdf9c17aba61b.png

1a21c6229ef944cda6e26361c3f3a925.png


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:把代码编译成动态链接库


三次握手


fc303998105a4f20820a8af024e496e8.png


第一次握手:客户端给服务器发送一个J信号。

第二次握手:服务器接收客户端发送的请求,并得到客户端发送的J信号(但这个信号不一定正确),所以服务器会返回J+1信号和一个K信号。

第三次握手:客户端接收服务器发送返回的J+1信号,如果正确表示客户端和服务器端通信正常,但是此时服务器端还不知道通信是否正常,所以客户端还需要将服务器发送的K进行处理,发送K+1信号给服务器端,告知服务器通信正常。


服务器和客户端进行TCP/IP通信的流程


8223b66365284c7794b6e835469885e3.png


服务器的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的数据报格式:


85d8fcc185344003b81f6d2476cf27be.png


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客户端和服务器端通信成功!!!


f56e568a3c6d4b39ae611458fe295a9f.png


UDP广播


广播所用的广播地址为255.255.255.255。


本地广播信息是不会被路由器转发。

目录
相关文章
|
2月前
|
NoSQL 网络协议 Linux
Redis的实现一:c、c++的网络通信编程技术,先实现server和client的通信
本文介绍了使用C/C++进行网络通信编程的基础知识,包括创建socket、设置套接字选项、绑定地址、监听连接以及循环接受和处理客户端请求的基本步骤。
54 6
|
2月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
126 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
2月前
|
机器学习/深度学习 数据可视化 计算机视觉
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
这篇文章详细介绍了如何通过可视化深度学习中每层特征层来理解网络的内部运作,并使用ResNet系列网络作为例子,展示了如何在训练过程中加入代码来绘制和保存特征图。
63 1
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
|
2月前
|
机器学习/深度学习 数据可视化 Windows
深度学习笔记(七):如何用Mxnet来将神经网络可视化
这篇文章介绍了如何使用Mxnet框架来实现神经网络的可视化,包括环境依赖的安装、具体的代码实现以及运行结果的展示。
54 0
|
2月前
|
机器学习/深度学习 编解码 算法
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
MobileNetV3是谷歌为移动设备优化的神经网络模型,通过神经架构搜索和新设计计算块提升效率和精度。它引入了h-swish激活函数和高效的分割解码器LR-ASPP,实现了移动端分类、检测和分割的最新SOTA成果。大模型在ImageNet分类上比MobileNetV2更准确,延迟降低20%;小模型准确度提升,延迟相当。
61 1
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
|
2月前
|
机器学习/深度学习 网络架构 计算机视觉
目标检测笔记(一):不同模型的网络架构介绍和代码
这篇文章介绍了ShuffleNetV2网络架构及其代码实现,包括模型结构、代码细节和不同版本的模型。ShuffleNetV2是一个高效的卷积神经网络,适用于深度学习中的目标检测任务。
81 1
目标检测笔记(一):不同模型的网络架构介绍和代码
|
2月前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
148 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
2月前
|
机器学习/深度学习 数据采集 算法
目标分类笔记(一): 利用包含多个网络多种训练策略的框架来完成多目标分类任务(从数据准备到训练测试部署的完整流程)
这篇博客文章介绍了如何使用包含多个网络和多种训练策略的框架来完成多目标分类任务,涵盖了从数据准备到训练、测试和部署的完整流程,并提供了相关代码和配置文件。
59 0
目标分类笔记(一): 利用包含多个网络多种训练策略的框架来完成多目标分类任务(从数据准备到训练测试部署的完整流程)
|
2月前
|
编解码 人工智能 文件存储
轻量级网络论文精度笔记(二):《YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object ..》
YOLOv7是一种新的实时目标检测器,通过引入可训练的免费技术包和优化的网络架构,显著提高了检测精度,同时减少了参数和计算量。该研究还提出了新的模型重参数化和标签分配策略,有效提升了模型性能。实验结果显示,YOLOv7在速度和准确性上超越了其他目标检测器。
49 0
轻量级网络论文精度笔记(二):《YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object ..》
|
2月前
|
机器学习/深度学习 Python
深度学习笔记(九):神经网络剪枝(Neural Network Pruning)详细介绍
神经网络剪枝是一种通过移除不重要的权重来减小模型大小并提高效率的技术,同时尽量保持模型性能。
55 0
深度学习笔记(九):神经网络剪枝(Neural Network Pruning)详细介绍