函数介绍:
1.socket()
作用:创建套接字
domain:
AF_INET
:IPv4 Internet 协议族。AF_INET6
:IPv6 Internet 协议族。AF_UNIX
:Unix 域协议族,用于在同一台主机上的进程间通信。type:
SOCK_STREAM
:提供序列化的、可靠的、双向连接的字节流服务,通常用于TCP连接。SOCK_DGRAM
:提供无连接的、不可靠的数据报服务,通常用于UDP通信。protocol:
这个参数指定了使用的特定协议。对于很多常见的套接字类,如
SOCK_STREAM
和SOCK_DGRAM
,这个参数可以设置为0,系统会自动选择一个默认协议。return value:
socket()
函数调用成功时,返回一个非负整数,即新创建的套接字的文件描述符(socket descriptor)。如果调用失败,函数返回-1,并且errno
被设置为描述错误的值。
服务端:
2.bind()
作用:将套接字(fd)和ip和port绑定起来
sockfd:
- 要绑定的文件描述符,也就是socket的返回值
addr:
指向
sockaddr
结构体的指针,该结构体包含了套接字的地址信息。
- sin_family:协议家族
- sin_port:要绑定的端口。
- sin_addr.s_addr:要绑定的ip
介绍以下里面的函数:
h表示host,n表示network
htons
通常用于端口号,htonl
通常用于IP地址作用:将IPv4地址的点分十进制字符串表示转换为网络字节序的二进制值
INADDR_ANY:用于指定一个特殊的IPv4地址
0.0.0.0
,表示“接受任何可用的网络接口”,通常用在服务器监听上。addrlen: 大小
return value:
成功返回0,失败返回-1并设置错误码。
3.listen()
作用:将套接字设置为监听状态可以监听链接请求
sockfd:socket()的返回值
backlog:一个大于0的整数(后续补充)
return value:成功返回0失败返回-1并设置错误码
4.accept()
作用:接受链接请求
sockfd:socket()的返回值
addr和addrlen:输出型参数,用于存储连接客户端的地址信息。
return value:
成功返回一个新的文件描述符用于与连接的客户端进行通信。之后的read和write传入的都是accept的返回值。
失败返回-1,设置错误码。
客户端:
2.connect()
作用:向服务端发起链接
sockfd:socket()的返回值
addr:要连接的服务端的地址信息
addrlen:addr的大小
return value:
成功返回0,失败返回-1并设置错误码。
代码:
tcpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "log.hpp"
#include "threadpool.hpp"namespace server
{enum{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR,OPEN_ERR};static const uint16_t gport = 8080;static const int gbacklog = 5; // 底层链接队列的长度class TcpServer;class ThreadData{public:ThreadData(TcpServer *self, int sock) : _self(self), _sock(sock){}public:TcpServer *_self;int _sock;};class TcpServer{public:TcpServer(const uint16_t &port = gport) : _listensock(-1), _port(port){}void initServer(){// 1.创建socket文件套接字对象_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){logMessage(FATAL, "create socket error");exit(SOCKET_ERR);}logMessage(NORMAL, "create socket success:%d", _listensock);// 2.bind绑定自己的网络信息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(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind socket error");exit(BIND_ERR);}logMessage(NORMAL, "bind socket success");// 3.设置socket为监听状态,获取新的客户端链接if (listen(_listensock, gbacklog) < 0){logMessage(FATAL, "listen socket error");exit(LISTEN_ERR);}logMessage(NORMAL, "listen socket success");}void start(){// 4.线程池初始化ThreadPool<Task>::getInstance()->run();logMessage(NORMAL, "Thread init success");// signal(SIGCHLD,SIG_IGN);//忽略这个信号for (;;){// 4.server获取新链接// sock,和client进行通信的fdstruct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_listensock, (struct sockaddr *)&peer, &len);if (sock < 0){logMessage(ERROR, "accept error");continue;}logMessage(NORMAL, "accept a new link success,get a new sock:%d", sock);serviceIO(sock);close(sock);//对一个已经使用完毕的sock,我们必须要关闭这个sock,要不然会导致文件描述符泄漏}}// static void *threadRoutine(void *args)// {// pthread_detach(pthread_self());// ThreadData *td = static_cast<ThreadData *>(args);// td->_self->serviceIO(td->_sock);// delete td;// close(td->_sock);// return nullptr;// }~TcpServer() {}private:int _listensock;uint16_t _port;};
}
// 5.这里就是一个sock,未来通信我们就用这个sock,面向字节流的,后续全部都是文件操作!
// serviceIO(sock);
// close(sock);//对一个已经使用完毕的sock,我们必须要关闭这个sock,要不然会导致文件描述符泄漏// version2,多进程版,多线程版,线程池版
// version1,多进程版
// pid_t id = fork();
// if(id == 0)//child
// {
// close(_listensock);
// if(fork()>0) exit(0);//关闭child,创建了一个孙子。因为child退了,孙子进程成了孤儿进程,由操作系统回收
// serviceIO(sock);
// close(sock);//对一个已经使用完毕的sock,我们必须要关闭这个sock,要不然会导致文件描述符泄漏
// exit(0);
// }
// father
// pid_t ret = waitpid(id,nullptr,0);
// if(ret>0)
// {
// std::cout<<"wait success:"<<ret<<std::endl;
// }// version2,多进程版
// pid_t id = fork();
// if(id == 0)//child
// {
// close(_listensock);
// serviceIO(sock);
// close(sock);//对一个已经使用完毕的sock,我们必须要关闭这个sock,要不然会导致文件描述符泄漏
// exit(0);
// }
// close(sock);// version3:多线程版
// pthread_t tid;
// ThreadData* td = new ThreadData(this,sock);
// pthread_create(&tid,nullptr,threadRoutine,td);// version4:线程池
//ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));
tcpServer.cc
#include"tcpServer.hpp"
#include<memory>
using namespace server;
using namespace std;
static void Usage(string proc)
{cout<<".\nUsage:\n\t"<<proc<<"local_port\n\n";
}
//tcp服务器,启动上和udp Server一模一样
//./tcpServer local_port
int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);unique_ptr<TcpServer> tsvr(new TcpServer(port));tsvr->initServer();tsvr->start();return 0;}
tcpClient.hpp
#pragma once
#include <iostream>
#include<string>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<cstring>
#define NUM 1024
class TcpClient
{public:TcpClient(const std::string &serverip,const uint16_t &serverport):_sock(-1),_serverip(serverip),_serverport(serverport){}void initClient(){//1.创建socket_sock = socket(AF_INET,SOCK_STREAM,0);if(_sock < 0){std::cerr<<"socket create error"<<std::endl;exit(2);}//2.客户端不用显示的bind//3.要不要listen?没人连我啊//4.要不要accept?no//5.要什么呢?要发起链接!}void start(){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());if(connect(_sock,(struct sockaddr*)&server,sizeof(server)) != 0){std::cerr<<"socket connect error"<<std::endl;}else{std::string msg;while(1){std::cout<<"Enter#";std::getline(std::cin,msg);write(_sock,msg.c_str(),msg.size());char buffer[NUM];int n = read(_sock,buffer,sizeof(buffer)-1);if(n>0){//目前把读到的数据当字符串buffer[n] = 0;std::cout<<"Server回显#"<<buffer<<std::endl;}else{break;}}}}~TcpClient(){if(_sock >= 0)close(_sock);//文件描述符的生命周期随进程,所以这里不写也行}
private:int _sock;std::string _serverip;uint16_t _serverport;};
tcpClient.cc
#include"tcpClient.hpp"
#include<memory>
using namespace std;
static void Usage(string proc)
{cout<<".\nUsage:\n\t"<<proc<<"serverip serverport\n\n";
}
//./tcpClient serverip serverport
int main(int argc,char* argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip = argv[1];uint16_t serverport = atoi(argv[2]);unique_ptr<TcpClient> tcli(new TcpClient(serverip,serverport));tcli->initClient();tcli->start();return 0;
}
Task.hpp
#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <functional>
#include <unistd.h>
#include <string.h>
#include"log.hpp"
void serviceIO(int sock)
{char buffer[1024];while (true){ssize_t n = read(sock, buffer, sizeof(buffer) - 1);if (n > 0){// 目前我们把读到的数据当成字符串,截止目前// 为什么UDP并不符合文件特性,文件也是字节流的,UDP是数据报的,所以要用特殊函数来读buffer[n] = 0;std::cout << "recv messages:" << buffer << std::endl;write(sock, buffer, strlen(buffer)); // 多路转接}else if (n == 0){// 代表client退出logMessage(NORMAL, "client quit,me too!");break;}}close(sock);
}class Task
{using func_t = std::function<void(int)>;public:Task() {}Task(int sock, func_t func): _sock(sock), _callback(func){}void operator()(){_callback(_sock);}private:int _sock;func_t _callback;
};
log.hpp
#pragma once
#include<iostream>
#include<string>
#include<cstdarg>
#include<ctime>
#include<unistd.h>
//cat /var/log/messages系统日志
//日志等级
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4//致命错误const char* to_levelstr(int level)
{switch(level){case DEBUG:return "DEBUG";case NORMAL:return "NORMAL";case WARNING:return "WARNING";case ERROR:return "ERROR";case FATAL:return "FATAL";}
}
// void logMessage(int level,const char* messages)
// {
// std::cout<<messages<<std::endl;
// }
// void logMessage(DEBUG,"hello %f ,%d,%c",3.14,10,'C');
void logMessage(int level,const char* format,...)
{//[日志等级][时间戳/时间][pid][message]//暂定// va_list start;// va_start(start);// while(*p)// {// switch(*p)// {// case '%':// p++;// if(*p == 'f') arg = va_arg(start,float);// ...// }// }// va_end(start);#define NUM 1024char logprefix[NUM];snprintf(logprefix,sizeof(logprefix),"[%s][%ld][pid:%d]",to_levelstr(level),(long int)time(nullptr),getpid());char logcontent[NUM];va_list arg;va_start(arg,format);vsnprintf(logcontent,sizeof(logcontent),format,arg);std::cout<<logprefix<<logcontent<<std::endl;
}
makefile
cc=g++
.PHONY:all
all:tcpClient tcpServertcpClient:tcpClient.cc$(cc) -o $@ $^ -std=c++11 -lpthread
tcpServer:tcpServer.cc$(cc) -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f tcpClient tcpServer