文章目录
- 网络编程套接字
- 1. 认识TCP协议
- 2. 认识UDP协议
- 3. 网络字节序
- 4. socket编程接口
- 4.1 sockaddr 结构
- 5. 简单的UDP网络程序
- 6. 简单的TCP网络程序
- 6.1 TCP socket的封装
- 6.2 TCP协议通讯流程
网络编程套接字
1. 认识TCP协议
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
2. 认识UDP协议
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据包
3. 网络字节序
不管这台主机是大端还是小端,就需要先将数据转换为大端字节序
h表示本地,n 表示 network, l表示32为长整数, s表示16位短整数
4. socket编程接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
4.1 sockaddr 结构
5. 简单的UDP网络程序
void InitServer()
{_sock = socket(AF_INET, SOCK_DGRAM, 0);if (_sock < 0){std::cout << "create sock error" << strerror(errno) << std::endl;exit(SOCKET_ERR);}std::cout << "create sock success" << std::endl;struct sockaddr_in local;sizeof (local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());if (bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0){std::cout << "bind socket error" << strerror(errno) << std::endl;exit(BIND_ERR);}std::cout << "bind socket success" << std::endl;
}
下面是实现的简单群聊服务器:
-
环形队列
#pragma once#include <iostream> #include <vector> #include <pthread.h> #include <semaphore.h>static const int N = 50;template <class T> class RingQueue { private:void P(sem_t &s){sem_wait(&s);}void V(sem_t &s){sem_post(&s);}void Lock(pthread_mtuex_t &m){pthread_mutex_lock(&m);}void Unlock(pthread_mutex_t &m){pthread_mutex_unlock(&m);}public:RingQueue(int num = N) : _ring(num), _cap(num){sem_init(&_data_sem, 0, 0);sem_init(&_space_sem, 0, num);_c_step = _p_step = 0;pthread_mutex_init(&_c_mutex, nullptr);pthread_mutex_init(&_p_mutex, nullptr);}void push(const T &in){P(_space_sem);Lock(_p_mutex);_ring[_p_step++] = in;_p_step %= _cap;Unlock(_p_mutex);V(_data_sem);}void pop(T *out){P(_data_sem);Lock(_c_mutex);*out = _ring[_c_step++];_c_step %= _cap;Unlock(_c_mutex);V(_space_sem);}~RingQueue(){sem_destroy(&_data_sem);sem_destroy(&_space_sem);pthread_mutex_destroy(&_p_mutex);pthread_mutex_destroy(&_c_mutex);}private:std::vector<T> _ring;int _cap; // 环形队列的大小sem_t _data_sem; // 消费者关心sem_t _space_sem; // 生产者关心int _c_step; // 消费位置int _p_step; // 生产位置pthread_mutex_t _c_mutex;pthread_mutex_t _p_mutex; };
-
线程包装
#pragma once#include <iostream> #include <string> #include <cstdlib> #include <pthread.h> #include <functional>class Thread { public:typedef enum{NEW = 0, RUNNING, EXITED} ThreadStatus;using func_t = std::function<void ()>;public:Thread(int num, func_t func) : _tid(0), _status(NEW), _func(func){char name[128];snprintf(name, sizeof(name), "thread-%d", num);_name = name; }int status() {return _status;}std::string threadname(){return _name;}pthread_t pthreadid(){if (_status == RUNNING){return _tid;}else{return 0;}}void operator()() // 仿函数{if (_func != nullptr) _func();}static void *runHelper(void *args){Thread* ts = (Thread*)args;(*ts)();return nullptr;}void run(){int n = pthread_create(&_tid, nullptr, runHelper, this);if (n != 0) exit(1);_status = RUNNING;}void join(){int n = pthread_join(_tid, nullptr);if (n != 0){std::cout << "man thread join thread" << _name << " error" << std::endl;return;}_status = EXITED;}~Thread(){}private:pthread_t _tid;std::string _name;func_t _func;ThreadStatus _status; };
-
锁包装
#pragma once#include <iostream> #include <pthread.h>class Mutex // 自己不维护锁,外部传入 { private:pthread_mutex_t *_pmutex; public:Mutex(pthread_mutex_t *mutex) : _pmutex(mutex){}void lock(){pthread_mutex_lock(_pmutex);}void unlock(){pthread_mutex_unlock(_pmutex);}~Mutex(){} };class lockGuard { private:Mutex _mutex; public:lockGuard(pthread_mutex_t *mutex) : _mutex(mutex){_mutex.lock();}~lockGuard(){_mutex.unlock();} };
-
服务器
#pragma once#include <iostream> #include <cerrno> #include <cstring> #include <cstdlib> #include <functional> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include <unordered_map> #include "err.hpp" #include "RingQueue.hpp" #include "lockGuard.hpp" #include "Thread.hpp"const static uint16_t port = 8888; using func_t = std::function<std::string(std::string)>;class UdpServer { private:uint16_t _port;int _sock;std::unordered_map<std::string, struct sockaddr_in> _onlineuser;pthread_mutex_t _lock;RingQueue<std::string> _rq;Thread *c;Thread *p;public:UdpServer(uint16_t port = port) : _port(port){std::cout << "server addr " << _port << std::endl;pthread_mutex_init(&_lock, nullptr);p = new Thread(1, std::bind(&UdpServer::Recv, this));c = new Thread(1, std::bind(&UdpServer::Broadcast, this));}void start(){_sock = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字if (_sock < 0){std::cout << "create _sock error" << std::endl;exit(SOCKET_ERR);}std::cout << "create _sock success" << std::endl;struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_port = htons(_port);local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0) // 绑定套接字{std::cout << "bind socket error" << std::endl;exit(BIND_ERR);}std::cout << "bind socket success" << std::endl;p->run();c->run();}void addUser(const std::string &name, const struct sockaddr_in &peer){lockGuard guard(&_lock);auto it = _onlineuser.find(name);if (it != _onlineuser.end()){return;}_onlineuser.insert(std::pair<const std::string, const struct sockaddr_in>(name, peer));}void Recv(){char buf[2056];while (true){struct sockaddr_in peer;socklen_t len = sizeof(peer);int n = recvfrom(_sock, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0){buf[n] = '\0';}elsecontinue;std::cout << "recv done" << std::endl;std::string clientip = inet_ntoa(peer.sin_addr);uint16_t clientport = ntohs(peer.sin_port);std::cout << clientip << "-" << clientport << "#" << buf << std::endl;std::string name = clientip;name += "-";name += std::to_string(clientport);addUser(name, peer);_rq.push(buf);}}void Broadcast(){while (true){std::string sendstring;_rq.pop(&sendstring);std::vector<struct sockaddr_in> v;{lockGuard guard(&_lock);for (auto user : _onlineuser){v.push_back(user.second);}}for (auto user : v){sendto(_sock, sendstring.c_str(), sendstring.size(), 0, (struct sockaddr*)&(user), sizeof(user));std::cout << "send done" << sendstring << std::endl;}}}~UdpServer(){pthread_mutex_destroy(&_lock);c->join();p->join();delete p, c;} };
地址转换函数
inet_ntoa
是把返回结果放到了静态区,这个时候不需要手动释放。如果多次调用,会覆盖掉上一次的值6. 简单的TCP网络程序
TCP由于是全双工的,所以初始化工作一共有五步
- socket
- bind
- listen
- accept
- connect
listen
声明socket fd处于监听状态,并且允许多个客户端来连接等待状态accept
三次握手完成后,调用服务器连接,
connect
客户端需要调用这个函数连接服务器
TCP 简单服务器:
#pragma once#include <iostream>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "err.hpp"static const int defaultport = 8888;
static const int backlog = 32;
using func_t = std::function<std::string(const std::string &)>;class TcpServer;class ThreadData
{
public:int _sock;std::string _clientip;uint16_t _port;TcpServer *_current;
public: ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *ts): _sock(fd), _clientip(ip), _port(port), _current(ts){}
};class TcpServer
{
public:TcpServer(func_t func, uint16_t port = defaultport) : _port(port), _func(func){}void initServer(){_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cout << "create socket error" << std::endl;exit(SOCKET_ERR);}struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = htonl(INADDR_ANY);local.sin_port = htons(_port);if (bind(_listensock, (struct sockaddr*)&local, sizeof(local)) < 0){std::cout << "create bind error" << std::endl;exit(BIND_ERR);}if (listen(_listensock, backlog) < 0){std::cout << "create listen error" << std::endl;exit(LISTEN_ERR);}}static void* threadRuntinue(void* args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData*>(args);td->_current->server(td->_sock, td->_clientip, td->_port);delete td;return nullptr;}void start(){_quit = false;while (true){struct sockaddr_in client;socklen_t len = sizeof(client);int sock = accept(_listensock, (struct sockaddr*)&client, &len); if (sock < 0){std::cout << "create accept error" << std::endl;continue;}std::string clientip = inet_ntoa(client.sin_addr);uint16_t clientport = ntohs(client.sin_port);std::cout << "accept success" << clientip << " " << clientport << std::endl;// server(sock, clientip, clientport); 第一个版本// 多线程版本pthread_t tid;ThreadData *threadDate = new ThreadData(sock, clientip, clientport, this);pthread_create(&tid, nullptr, threadRuntinue, threadDate);}}void server(int sock, const std::string ip, uint16_t port){std::string who = ip + "-" + std::to_string(port);char buffer[1024];while (true){size_t s = read(sock, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;std::string res = _func(buffer); // 回调传进来的函数std::cout << who << ">>>" << res << std::endl;write(sock, res.c_str(), res.size());}else if (s == 0){close(sock);std::cout << who << "quit, me too" << std::endl;break;}else{close(sock);std::cout << "read error" << std::endl;break;}}}~TcpServer() {}
private:uint16_t _port;int _listensock;bool _quit;func_t _func;
};
TCP 简单的客户端
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "err.hpp"int main(int argc, char *argv[])
{std::string serverip = argv[1];uint16_t serverport = atoi(argv[2]);int sock = socket(AF_INET, SOCK_STREAM, 0);// 要不要bind? 要// 要不要自己bind? 不要,因为client要让OS自动给用户进行bind// 要不要listen?不要 要不要accept?不需要struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);inet_aton(serverip.c_str(), &(server.sin_addr));int cnt = 5;while (connect(sock, (struct sockaddr*)&server, sizeof(server)) != 0){sleep(1);std::cout << "正在尝试重新连接" << std::endl;cnt--;if (cnt < 0) break;}if (cnt <= 0) {std::cout << "连接失败" << std::endl;exit(CONNECT_ERR);}char buffer[1024];while (true){std::string line;std::getline(std::cin, line);write(sock, line.c_str(), line.size());size_t s = read(sock, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;std::cout << "server echo" << std::endl;}else if (s == 0){std::cout << "server quit" << std::endl;break;} else {std::cout << "read error" << std::endl;break;}}close(sock);return 0;
}
6.1 TCP socket的封装
#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "Err.hpp"static const int gbacklog = 32;
static const int defaultfd = -1;class Sock
{
public:Sock() : _sock(defaultfd) {}void Socket(){_sock = socket(AF_INET, SOCK_STREAM, 0);if (_sock < 0){logMessage(Fatal, "socket error, code : %d", errno);exit(SOCKET_ERR);}}void Bind(const uint16_t &port){struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(Fatal, "bind error, code: %d, errstring: %s", errno, strerror(errno));exit(BIND_ERR);}}void Listen(){if (listen(_sock, gbacklog) < 0){logMessage(Fatal, "listen error, code: %d, errstring: %s", errno, strerror(errno));exit(LISTEN_ERR);}}int Accept(std::string *clientip, uint16_t *clientport){struct sockaddr_in temp;socklen_t len = sizeof(temp);int sock = accept(_sock, (struct sockaddr *)&temp, &len);if (sock < 0){logMessage(Warning, "accept error, code: %d, errstring: %s", errno, strerror(errno));}else{*clientip = inet_ntoa(temp.sin_addr);*clientport = ntohs(temp.sin_port);}return sock;}int Connect(const std::string &serverip, const uint16_t &serverport){struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());return connect(_sock, (struct sockaddr *)&server, sizeof(server));}int Fd(){return _sock;}~Sock(){if (_sock != defaultfd)close(_sock);}private:int _sock;
};
6.2 TCP协议通讯流程
服务器初始化:
- 调用socket,创建文件描述符
- 调用bind,将当前的文件描述符和ip / port 绑定在一起,如果这个端口被占用了,就会bind失败
- 调用listen,声明这个文件描述符是服务器的文件描述符,为后面的accept做好准备
- 调用accept并阻塞,等待客户端连接
建立连接的过程:
- 调用socket,创建文件描述符
- 调用connect,向服务器发起连接请求
- connect会发出SYN并阻塞等待服务器应答
- 服务器收到客户端的SYN,会应答一个SYN-ACK,表示同意建立连接
- 客户端收到SYN-ACK后会从connect()返回,同时应答一个ACK
这个建立过程称为三次握手
TCPVSUDP
- 可靠传输 VS 不可靠传输
- 有连接 VS 无连接
- 字节流 VS 数据报