网络编程套接字
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 <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'; } else continue; 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 数据报
7. 守护进程
// 1. setsid(); // 2. setsid(), 调用进程,不能是组长!我们怎么保证自己不是组长呢? // 3. 守护进程a. 忽略异常信号 b. 0,1,2要做特殊处理 c. 进程的工作路径可能要更改 / #include <cstdlib> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include "log.hpp" #include "err.hpp" //守护进程的本质:是孤儿进程的一种! void Daemon() { // 1. 忽略信号 signal(SIGPIPE, SIG_IGN); signal(SIGCHLD, SIG_IGN); // 2. 让自己不要成为组长 if (fork() > 0) exit(0); // 3. 新建会话,自己成为会话的话首进程 pid_t ret = setsid(); if ((int)ret == -1) { logMessage(Fatal, "deamon error, code: %d, string: %s", errno, strerror(errno)); exit(SETSID_ERR); } // 4. 可选:可以更改守护进程的工作路径 // chdir("/") // 5. 处理后续的对于0,1,2的问题 int fd = open("/dev/null", O_RDWR); if (fd < 0) { logMessage(Fatal, "open error, code: %d, string: %s", errno, strerror(errno)); exit(OPEN_ERR); } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); }