函數介紹:?
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