目录
1、一些网络中的名词
1.1 IP地址
1.2 端口号port
1.3 "端口号" 和 "进程ID"
1.4 初始TCP协议
1.5 UDP协议
2、socket编程接口
2.1 socket 常见API
2.2 sockaddr结构
3、简单的网络程序
3.1 udp实现echo服务器和客户端
3.1.1 echo服务器实现
3.1.2 echo客户端实现
3.1.3 运行结果
3.2 tcp实现echo服务器和客户端
3.2.1 多进程的echo服务器
3.2.2 基于线程池tcp的echo服务器
3.3 代码中的一些函数
3.3.1 地址转换函数
3.3.2 udp使用的的函数
3.3.3 tcp使用的函数
4、结语
1、一些网络中的名词
1.1 IP地址
IP地址就和我们现实中的地址是一个概念,只不过一个在网络中定位,一个在现实中定位,
在一台服务器往另一台服务器发送数据的时候,IP数据包的头部中,有两个IP地址,一个是源IP地址,另一个是目的IP地址,
1.2 端口号port
端口号(port)是传输层协议的内容.
端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
一个端口号只能被一个进程占用.
1.3 "端口号" 和 "进程ID"
pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程。一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定。
1.4 初始TCP协议
传输层协议
有连接
可靠传输
面向字节流
1.5 UDP协议
传输层协议
无连接
不可靠传输
面向数据报
网络字节序
在计算机的内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
2、socket编程接口
2.1 socket 常见API
// 创建 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);
2.2 sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同.
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.
IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;
sockaddr 结构
struct sockaddr{__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */char sa_data[14]; /* Address data. */};
sockaddr_in 结构
struct sockaddr_in{__SOCKADDR_COMMON (sin_);in_port_t sin_port; /* Port number. */struct in_addr sin_addr; /* Internet address. *//* Pad to size of `struct sockaddr'. */unsigned char sin_zero[sizeof (struct sockaddr) -__SOCKADDR_COMMON_SIZE -sizeof (in_port_t) -sizeof (struct in_addr)];};
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址.
in_addr结构
struct in_addr{in_addr_t s_addr;};
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;
3、简单的网络程序
3.1 udp实现echo服务器和客户端
3.1.1 echo服务器实现
//udp_server.hpp
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unordered_map>
#include <vector>#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>class udpserver{
public:udpserver(std::string ip, int16_t port):_fd(-1), _ip(ip), _port(port),_users(0){}~udpserver(){if (_fd > 0) {close(_fd);}}void initServer() {_fd = socket(AF_INET, SOCK_DGRAM, 0);if (_fd < 0) {perror("注册socket失败");exit(2);}struct sockaddr_in local;memset(&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(_fd, (struct sockaddr*)&local, sizeof(local)) < 0) {perror("绑定失败!");exit(3);}//std::cout << "绑定成功!"<< std::endl;}void startServer(){//准备用来接收客户端发送的消息的缓冲区char buffer[1024];while (1) {//准备用来接收发送消息的客户端信息memset(buffer, '\0', sizeof(buffer));struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);//接收数据,以及接收发送数据的客户端信息//std::cout << "正在接收!" << std::endl;ssize_t recv_size = recvfrom(_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);//打印客户端发送来的数据//std::cout << "接收成功!正在打印:" << std::endl;// if (recv_size > 0) {// buffer[recv_size] = 0;// std::string ip = inet_ntoa(peer.sin_addr);// int16_t port = ntohs(peer.sin_port);// std::cout << "[" << ip << ":" << port << "]:";// std::cout << buffer << std::endl;// }//处理数据buffer[recv_size] = 0;std::string massage;massage += inet_ntoa(peer.sin_addr);massage += ":";massage += ntohs(peer.sin_port);//_users.insert(make_pair<std::string,struct sockaddr_in>(massage, peer);_users.insert({massage, peer});massage += "#";massage += buffer;//_users.insert(makepair(, peer);//回写数据//sendto(_fd, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, len);for (auto &s : _users) {sendto(_fd, massage.c_str(), massage.size(),0 ,(struct sockaddr*)&(s.second), sizeof(s.second));}}}private:int _fd;std::string _ip;int16_t _port;std::unordered_map<std::string,struct sockaddr_in> _users;
};
//udp_server.cpp
#include "udpserver.hpp"
#include <memory>int main(int argc, char* args[]) {std::string ip;int16_t port = 0;if (argc == 3) {ip = args[1];port = atoi(args[2]);}else if (argc == 2) {ip = "0.0.0.0";port = atoi(args[1]);}else{perror("输入错误!");return 1;}std::unique_ptr<udpserver> server(new udpserver(ip,port));server->initServer();server->startServer();return 0;
}
3.1.2 echo客户端实现
//udp_client.cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>struct sendData{int _sock;struct sockaddr_in *server;
};void* sending(void *arg) {struct sendData* data = (struct sendData*)arg;int sock = data->_sock;struct sockaddr_in server = *(data->server);while (1) {std::string massage;std::cerr << "请输入内容:" ;std::getline(std::cin, massage);//发送数据sendto(sock, massage.c_str(), massage.size(), 0, (struct sockaddr*)&server, sizeof(server));}
}void* receive(void *arg) {struct sendData* data = (struct sendData*)arg;int sock = data->_sock;char buffer[1024];while (1) {memset(buffer, '\0', sizeof(buffer));struct sockaddr_in from;socklen_t len = sizeof(from);ssize_t recv_size = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&from, &len);if(recv_size < 1) {continue;}buffer[recv_size] = '\0';//printf("[%s:%u]#%s\n",inet_ntoa(from.sin_addr),ntohs(from.sin_port),buffer);std::cout << buffer << std::endl;}
}//客户端,负责给服务端发送消息
int main(int argc, char* args[]) {if (argc != 3) {std::cerr << "请正确输入参数!" << std::endl;exit(1);}std::string ip = args[1];int16_t port = atoi(args[2]);//创建套接字int _sock = socket(AF_INET, SOCK_DGRAM, 0);//这里依然会绑定,但是不需要手动绑定,回自动绑定,在第一次send的时候自动绑定, if (_sock < 0) {exit(2);}struct sockaddr_in server;server.sin_addr.s_addr = inet_addr(ip.c_str());server.sin_family = AF_INET;server.sin_port = htons(port);socklen_t len = sizeof(server);sendData data;data._sock = _sock;data.server = &server;//创建线程,让线程1负责发送,线程2负责接收pthread_t send,recv;pthread_create(&send,nullptr,sending,(void*)&data);pthread_create(&send,nullptr,receive,(void*)&data);pthread_join(send,nullptr);pthread_join(recv,nullptr);close(_sock);return 0;
}
3.1.3 运行结果
3.2 tcp实现echo服务器和客户端
3.2.1 多进程的echo服务器
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <signal.h>#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>static void servise(int serviseSock, std::string userip, int16_t userport) {char buffer[1024];while (1) {memset(buffer, 0, sizeof(buffer));size_t s = read(serviseSock,buffer,sizeof(buffer)); if (s > 0) {buffer[s] = '\0';std::cout << userip.c_str() << ":" << userport << "#" << buffer << std::endl;}else if (s == 0) {//表示对方关闭了连接std::cerr << userip << ":" << userport << " shutdowm,me too!" << std::endl;break;}else {std::cerr << "read socket error," << errno << strerror(errno) << std::endl;break;}write(serviseSock, buffer, strlen(buffer));}
}class tcpServer{
public:tcpServer(int16_t port, std::string ip = ""):_ip(ip),_port(port),_listenSock(-1){}~tcpServer(){if (_listenSock > 0) {close(_listenSock);}}void initServer(){//backlog不能太大也不能太小static int gbacklog = 20;//申请描述符_listenSock = socket(AF_INET, SOCK_STREAM, 0);if (_listenSock < 0) {std::cerr << "注册socket失败" << std::endl;exit(2);}//绑定端口号和IP地址struct sockaddr_in local;local.sin_family = AF_INET;local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());local.sin_port = htons(_port);if (bind(_listenSock, (struct sockaddr*)&local, sizeof(local)) < 0) {std::cerr << "绑定ip和端口号失败" << std::endl;exit(3);}//设置监听状态if (listen(_listenSock, gbacklog) < 0) {std::cerr << "设置监听失败" << std::endl;exit(4);}}void start() {//将子进程的信号改为忽略signal(SIGCHLD, SIG_IGN);while (1) {struct sockaddr_in user;socklen_t len = sizeof(user);int serviseSock = accept(_listenSock, (struct sockaddr*)&user, &len);std::string userip = inet_ntoa(user.sin_addr);int16_t userport = ntohs(user.sin_port);//servise(serviseSock,userip,userport);int pid = fork();if (pid == 0) {close(_listenSock);servise(serviseSock,userip,userport);close(serviseSock);exit(0);}close(serviseSock);}}private:std::string _ip;int16_t _port;int _listenSock;
};
但是我们都知道,在操作系统中,进程是资源分配的基本单位,如果使用多进程的方案的话,就非常的浪费资源,所以,相比之下,使用多线程的方式回更好,我们在实现一个基于线程池的实现方式。
3.2.2 基于线程池tcp的echo服务器
//自己实现的循环队列,当中使用的锁和信号都是自己封装的,这里就不放代码了
//ringqueue.hpp
#include <iostream>
#include <vector>
#include "sem.hpp"
#include "mutex.hpp"template<class T>
class ringqueue {public:ringqueue(int capacity = 10):_ring_queue(capacity),_start(0),_tail(0),_space_sem(capacity),_data_sem(0),_mtx(){}void push(const T &in){_space_sem.p();_mtx.lock();_ring_queue[_start++] = in;_start %= _ring_queue.size();_data_sem.v();_mtx.unlock();}void pop(T & out){_data_sem.p();_mtx.lock();out = _ring_queue[_tail++];_tail %= _ring_queue.size();_space_sem.v();_mtx.unlock();}~ringqueue(){}private:std::vector<T> _ring_queue;int _start;int _tail;sem _space_sem;sem _data_sem;mutex _mtx;
};
//单例模式的线程池
//其中的线程也是自己进行封装的,不做代码展示
//thread_pool.hpp
#include "thread.hpp"
#include "ringQueue.hpp"
#include <ctime>
#include <unistd.h>template <class T>
struct poolData
{Thread* _self;ringqueue<T>* _rq;
};template <class T>
class Pool
{public:static Pool<T>* getpool(int num = 10){if (nullptr == _pool) {pthread_mutex_lock(&mtx);if (nullptr == _pool) {_pool = new Pool<T>(num);}pthread_mutex_unlock(&mtx);}return _pool;}private:Pool(int num) :_consumer(num),_rq(10){}Pool(const Pool& pool) = delete;Pool& operator=(const Pool& pool) = delete;
public:void strat(){poolData<T> condata[_consumer.size()];for (int i = 0; i < _consumer.size(); ++i) {_consumer[i] = new Thread(i);condata[i]._self = _consumer[i];condata[i]._rq = &_rq;_consumer[i]->create(consumer,&condata[i]);}}// 生产者void pushTask(T task){_rq.push(task);}// 消费者static void *consumer(void *args){poolData<T> *pd = (poolData<T>*)args;Thread *self = pd->_self;ringqueue<T> *rq = pd->_rq;std::cout << self->name() << " Successfully started!" << std::endl;while (true) {T t;rq->pop(t);(*t)();delete t;}}~Pool(){for (int i = 0; i < _consumer.size(); ++i) {_consumer[i]->join();delete _consumer[i];}}private:ringqueue<T> _rq;std::vector<Thread*> _consumer;static pthread_mutex_t mtx;static Pool<T>* _pool;
};template<class T>
Pool<T>* Pool<T>::_pool = nullptr;template<class T>
pthread_mutex_t Pool<T>::mtx = PTHREAD_MUTEX_INITIALIZER;
//tcp_server.hpp
#include "thread_pool.hpp"
#include "Task.hpp"#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <signal.h>#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>static void servise(int serviseSock, std::string & userip, int16_t userport) {char buffer[1024];while (1) {memset(buffer, 0, sizeof(buffer));size_t s = read(serviseSock,buffer,sizeof(buffer)); if (s > 0) {buffer[s] = '\0';std::cout << userip.c_str() << ":" << userport << "#" << buffer << std::endl;}else if (s == 0) {//表示对方关闭了连接std::cerr << userip << ":" << userport << " shutdowm,me too!" << std::endl;break;}else {std::cerr << "read socket error," << errno << strerror(errno) << std::endl;break;}write(serviseSock, buffer, strlen(buffer));}close(serviseSock);
}class tcpServer{
public:tcpServer(int16_t port, std::string ip = ""):_ip(ip),_port(port),_listenSock(-1),_pool_ptr(Pool<Task*>::getpool()){}~tcpServer(){if (_listenSock > 0) {close(_listenSock);}}void initServer(){//backlog不能太大也不能太小static int gbacklog = 20;//申请描述符_listenSock = socket(AF_INET, SOCK_STREAM, 0);if (_listenSock < 0) {std::cerr << "注册socket失败" << std::endl;exit(2);}//绑定端口号和IP地址struct sockaddr_in local;local.sin_family = AF_INET;local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());local.sin_port = htons(_port);if (bind(_listenSock, (struct sockaddr*)&local, sizeof(local)) < 0) {std::cerr << "绑定ip和端口号失败" << std::endl;exit(3);}//设置监听状态if (listen(_listenSock, gbacklog) < 0) {std::cerr << "设置监听失败" << std::endl;exit(4);}}void start() {_pool_ptr->strat();while (1) {struct sockaddr_in user;socklen_t len = sizeof(user);int serviseSock = accept(_listenSock, (struct sockaddr*)&user, &len);std::string userip = inet_ntoa(user.sin_addr);int16_t userport = ntohs(user.sin_port);Task *task = new Task(serviseSock, userip, userport, servise);_pool_ptr->pushTask(task);}}private:std::string _ip;int16_t _port;int _listenSock;Pool<Task*>* _pool_ptr;
};
//服务器入口,
//tcp_server.cpp
#include "tcp_server.hpp"
#include <memory>int main(int argc, char* args[]) {std::string ip;int16_t port;if (argc == 2) {ip = "";port = atoi(args[1]);}else if (argc == 3) {ip = args[1];port = atoi(args[2]);}else {std::cerr << "输入错误!" << std::endl;exit(1);}std::unique_ptr<tcpServer> server(new tcpServer(port,ip));server->initServer();server->start();return 0;
}
3.2.3 运行结果
3.3 代码中的一些函数
3.3.1 地址转换函数
本节基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;
字符串与in_addr的一些函数:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
3.3.2 udp使用的的函数
发送函数sendto:
#include <sys/types.h>
#include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
接收函数recvfrom:
#include <sys/types.h>
#include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
3.3.3 tcp使用的函数
发送函数
#include <sys/types.h>
#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);
#include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);
接收函数
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);
4、结语
本文中若有错误,请私信或评论指出,谢谢!