函数介绍:
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
cpp
#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进行通信的fd
struct 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
cpp
#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
cpp
#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
cpp
#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
cpp
#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
cpp
#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 1024
char 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
cpp
cc=g++
.PHONY:all
all:tcpClient tcpServer
tcpClient:tcpClient.cc
$(cc) -o $@ $^ -std=c++11 -lpthread
tcpServer:tcpServer.cc
$(cc) -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f tcpClient tcpServer