(。・∀・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~https://blog.csdn.net/ky233?type=blog
点个关注不迷路⌯'▾'⌯
目录
一、接口介绍
1.socket
2.listen
3.accept
3.connect
4.send
二.基础代码
1.tcp_server
2.tcp_server.hpp
3.tcp_client
4.结果
三、多进程版本
四、多线程版本
五、线程池版本
六、变成小写转换大写的方法
一、接口介绍
1.socket
不做过多介绍,前文有
SOCK_STREM,TCP用这个
2.listen
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
将TCP套接字设置成监听状态
- 参数一:创建好的套接字
- 参数二:目前无法理解,
3.accept
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
获取链接
- 参数一:套接字
- 参数二:输出型参数
- 参数三:输出输入型参数
- 返回值:成功返回成功的套接字,失败则返回-1
3.connect
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
根据创建好的套接字,让客户端具有连接服务器的能力,会自动绑客户端的ip和端口
- 参数一:套接字
- 参数二:输出型参数
- 参数三:输入型参数
4.send
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
基于TCP向目标返回消息
参数四:一般为0
5.recv
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
基于TCP来读消息
参数四:一般为0
二.基础代码
1.tcp_server
#pragma once
#include "tcp_server.hpp"
#include <memory>
static void usage(std::string proc)
{
std::cout<<"\nUsage:"<<"proc\n"<<std::endl;
}
int main(int argc,char* argv[])
{
if (argc!=2)
{
usage(argv[0]);
exit(1);
}
//存储端口号
uint16_t port=atoi(argv[1]);
std::unique_ptr<TcpServer> svr(new TcpServer(port));
svr->initServer();
svr->start();
return 0;
}
2.tcp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
static void service(int sock, const std::string &client_ip, const uint16_t &client_port)
{
// echo server
char buffer[1024];
while (1)
{
// 先读取
// read和write可以直接被使用,套接字也就是文件描述符
ssize_t s = read(sock, &buffer, sizeof buffer);
if (s > 0)
{
buffer[s] = 0; // 直接将发送过来的数据当作字符串打印
std::cout << client_ip << ":" << client_port << "# " << buffer << std::endl;
}
else if (s = 0) // 对端关闭连接
{
logMessage(NORMAL, "%s:%d shutdown, me too!", client_ip.c_str(), client_port);
break;
}
else // 读取失败
{
logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
break;
}
write(sock, &buffer, sizeof buffer);
}
}
class TcpServer
{
const static int g_backlog = 20; // 不能太大也不能太小
public:
TcpServer(uint16_t port, std::string ip = "")
: port_(port), ip_(ip), listensock_(-1)
{
}
void initServer()
{
// 1.创建套接字
listensock_ = socket(AF_INET, SOCK_STREAM, 0);
if (listensock_ < 0)
{
logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "create socket success, listensock_: %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 = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());
if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)))
{
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
// 因为TCP是面向连接的,所以正是通信的时候需要先建立连接
// 3.开始监听
if (listen(listensock_, g_backlog) < 0)
{
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "init server success");
}
void start()
{
signal(SIGCHLD,SIG_IGN);//子进程退出会像父进程发送SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
while (1)
{
// 4.获取连接,服务器要获取客户端的连接,这样来方便返回
struct sockaddr_in src;
socklen_t len = sizeof(src);
// listensock_和listensock_有什么区别呢?
// 其中listensock_通过这个套接字只是为了把底层的连接获取上来
// 而listensock_才是真正的进行连接的
int listensock = accept(listensock_, (struct sockaddr *)&src, &len);
if (listensock < 0)
{
logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
continue;
}
// 获取成功了
uint16_t client_port = ntohs(src.sin_port);
std::string client_ip = inet_ntoa(src.sin_addr);
logMessage(NORMAL, "link success, listensock_: %d | %s : %d |\n",
listensock, client_ip.c_str(), client_port);
// 开始进行通信
// 单进程循环版,只能够一次处理一个客户端,处理完成了才能处理下一个,很明显是不可以的
//这是因为单进程的,下面函数是个死循环,所以不退出就没办法获取新的连接
//service(listensock_, client_ip, client_port);
//多进程版本
//创建子进程,那么子进程可以打开父进程曾经打开的fd吗?
//是可以的,父进程创建子进程时候,子进程的文件描述符表,会继承自于父进程,所以会看到同一个文件
pid_t id= fork();
assert(id!=-1);
if(id==0)//子进程
{
//子进程会不会继承父进程打开的文件与文件描述符fd呢?这是肯定的
//子进程是来提供服务的,需不需要监听socket呢?
service(listensock_, client_ip, client_port);
close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
exit(0);//子进程退出会造成僵尸进程
}
close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
//父进程
//waitpid是阻塞式等待,也会占用资源无意义,所以这里用signal
}
}
~TcpServer()
{
}
private:
uint16_t port_;
std::string ip_;
int listensock_;
};
3.tcp_client
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void usage(std::string proc)
{
std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
<< std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(1);
}
// 获取ip和port
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
// 创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
std::cerr << "socket error" << std::endl;
exit(2);
}
// client是不需要显示bind的,因为如果bind了就是一个非常具体的端口号了,多个客户端可能会出现冲突,可能会导致启动失败,所以不需要显示的bind
// 让OS自动选择
// 但是客户端必须拥有连接的能力
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = ntohs(port);
server.sin_addr.s_addr = inet_addr(ip.c_str());
if (connect(sock, (struct sockaddr *)&server, sizeof(server)))
{
std::cerr << "connect error" << std::endl;
exit(3); // TODO
}
std::cout << "connect success" << std::endl;
//连接成功直接通信
while(1)
{
std::string line;
std::cout<< "请输入# ";
std::getline(std::cin,line);
//发送数据
send(sock,line.c_str(),line.size(),0);
char buffer[1024];
ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);
if(s>0)
{
buffer[s]=0;
std::cout << "server 回显# " << buffer << std::endl;
}
else if(s==0)
{
break;
}
else
{
break;
}
}
return 0;
}
4.结果
可以看到我们的server是由两个的,一个是子进程一个是父进程
三、多进程版本
上面的其实已经是多进程版本的了,但是我们使用的signal信号来实现子进程退出的,下面我们用另一种方式来实现
为什么我们创建了子进程之后还要再创建一下呢?这是因为我们让子进程退出之间再次fork一下,会留下一个孙子进程,孙子进程变成了孤儿进程,会被OS领养,会被OS自己退出,所以父进程等待是不会阻塞的!
tcp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include "log.hpp"
static void service(int sock, const std::string &client_ip, const uint16_t &client_port)
{
// echo server
char buffer[1024];
while (1)
{
// 先读取
// read和write可以直接被使用,套接字也就是文件描述符
ssize_t s = read(sock, &buffer, sizeof buffer);
if (s > 0)
{
buffer[s] = 0; // 直接将发送过来的数据当作字符串打印
std::cout << client_ip << ":" << client_port << "# " << buffer << std::endl;
}
else if (s = 0) // 对端关闭连接
{
logMessage(NORMAL, "%s:%d shutdown, me too!", client_ip.c_str(), client_port);
break;
}
else // 读取失败
{
logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
break;
}
write(sock, &buffer, sizeof buffer);
}
}
class TcpServer
{
const static int g_backlog = 20; // 不能太大也不能太小
public:
TcpServer(uint16_t port, std::string ip = "")
: port_(port), ip_(ip), listensock_(-1)
{
}
void initServer()
{
// 1.创建套接字
listensock_ = socket(AF_INET, SOCK_STREAM, 0);
if (listensock_ < 0)
{
logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "create socket success, listensock_: %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 = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());
if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)))
{
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
// 因为TCP是面向连接的,所以正是通信的时候需要先建立连接
// 3.开始监听
if (listen(listensock_, g_backlog) < 0)
{
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "init server success");
}
void start()
{
// signal(SIGCHLD,SIG_IGN);//子进程退出会像父进程发送SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
while (1)
{
// 4.获取连接,服务器要获取客户端的连接,这样来方便返回
struct sockaddr_in src;
socklen_t len = sizeof(src);
// listensock_和servicesock有什么区别呢?
// 其中listensock_通过这个套接字只是为了把底层的连接获取上来
// 而servicesock才是真正的进行连接的
int servicesock = accept(listensock_, (struct sockaddr *)&src, &len);
if (servicesock < 0)
{
logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
continue;
}
// 获取成功了
uint16_t client_port = ntohs(src.sin_port);
std::string client_ip = inet_ntoa(src.sin_addr);
logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",
servicesock, client_ip.c_str(), client_port);
// 开始进行通信
// 单进程循环版,只能够一次处理一个客户端,处理完成了才能处理下一个,很明显是不可以的
// 这是因为单进程的,下面函数是个死循环,所以不退出就没办法获取新的连接
// service(listensock_, client_ip, client_port);
// 多进程版本
// 创建子进程,那么子进程可以打开父进程曾经打开的fd吗?
// 是可以的,父进程创建子进程时候,子进程的文件描述符表,会继承自于父进程,所以会看到同一个文件
// pid_t id= fork();
// assert(id!=-1);
// if(id==0)//子进程
// {
// //子进程会不会继承父进程打开的文件与文件描述符fd呢?这是肯定的
// //子进程是来提供服务的,需不需要监听socket呢?
// service(listensock_, client_ip, client_port);
// close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
// exit(0);//子进程退出会造成僵尸进程
// }
// close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
// //父进程
// //waitpid是阻塞式等待,也会占用资源无意义,所以这里用signal
// 2.2多进程版本,不用signal
pid_t id = fork();
if (id == 0)
{
if (fork() > 0)//这是因为我们让子进程退出之间再次fork一下,会留下一个孙子进程,孙子进程变成了孤儿进程
//会被OS领养,会被OS自己退出,所以父进程等待是不会阻塞的!
{
close(listensock_);
exit(0);
service(servicesock, client_ip, client_port);
exit(0);
}
}
// 父进程
waitpid(id, nullptr, 0);
close(servicesock);
}
}
~TcpServer()
{
}
private:
uint16_t port_;
std::string ip_;
int listensock_;
};
四、多线程版本
tcp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <pthread.h>
#include "log.hpp"
static void service(int sock, const std::string &client_ip, const uint16_t &client_port)
{
// echo server
char buffer[1024];
while (1)
{
// 先读取
// read和write可以直接被使用,套接字也就是文件描述符
ssize_t s = read(sock, &buffer, sizeof buffer);
if (s > 0)
{
buffer[s] = 0; // 直接将发送过来的数据当作字符串打印
std::cout << client_ip << ":" << client_port << "# " << buffer << std::endl;
}
else if (s = 0) // 对端关闭连接
{
logMessage(NORMAL, "%s:%d shutdown, me too!", client_ip.c_str(), client_port);
break;
}
else // 读取失败
{
logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
break;
}
write(sock, &buffer, sizeof buffer);
}
}
class ThreadData
{
public:
int sock_;
std::string ip_;
uint16_t port_;
};
class TcpServer
{
const static int g_backlog = 20; // 不能太大也不能太小
static void* threadRoutine(void* args)//这里我们要让新线程去干事情,肯定需要套接字,但是上面已经写了所以我们直接调用这个函数就可以了
{
pthread_detach(pthread_self());//为了避免内存泄漏要进行线程分离,让子线程执行完毕自动销毁
ThreadData *td=static_cast<ThreadData *>(args);
service(td->sock_, td->ip_,td->port_);
delete td;
return nullptr;
}
public:
TcpServer(uint16_t port, std::string ip = "")
: port_(port), ip_(ip), listensock_(-1)
{
}
void initServer()
{
// 1.创建套接字
listensock_ = socket(AF_INET, SOCK_STREAM, 0);
if (listensock_ < 0)
{
logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "create socket success, listensock_: %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 = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());
if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)))
{
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
// 因为TCP是面向连接的,所以正是通信的时候需要先建立连接
// 3.开始监听
if (listen(listensock_, g_backlog) < 0)
{
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "init server success");
}
void start()
{
// signal(SIGCHLD,SIG_IGN);//子进程退出会像父进程发送SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
while (1)
{
// 4.获取连接,服务器要获取客户端的连接,这样来方便返回
struct sockaddr_in src;
socklen_t len = sizeof(src);
// listensock_和servicesock有什么区别呢?
// 其中listensock_通过这个套接字只是为了把底层的连接获取上来
// 而servicesock才是真正的进行连接的
int servicesock = accept(listensock_, (struct sockaddr *)&src, &len);
if (servicesock < 0)
{
logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
continue;
}
// 获取成功了
uint16_t client_port = ntohs(src.sin_port);
std::string client_ip = inet_ntoa(src.sin_addr);
logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",
servicesock, client_ip.c_str(), client_port);
//3.0多线程版本
ThreadData *td = new ThreadData();
td->sock_ = servicesock;
td->ip_= client_ip;
td->port_ = client_port;
pthread_t tid;
//在多线程这里是不用关闭特定的文件描述符的,因为主线程与副线程是共享文件描述符的
pthread_create(&tid,nullptr,threadRoutine,td);
//close(servicesock)//走到最后主线程结束还是要关闭的
// 1.0开始进行通信
// 单进程循环版,只能够一次处理一个客户端,处理完成了才能处理下一个,很明显是不可以的
// 这是因为单进程的,下面函数是个死循环,所以不退出就没办法获取新的连接
// service(listensock_, client_ip, client_port);
// 2.0多进程版本
// 创建子进程,那么子进程可以打开父进程曾经打开的fd吗?
// 是可以的,父进程创建子进程时候,子进程的文件描述符表,会继承自于父进程,所以会看到同一个文件
// pid_t id= fork();
// assert(id!=-1);
// if(id==0)//子进程
// {
// //子进程会不会继承父进程打开的文件与文件描述符fd呢?这是肯定的
// //子进程是来提供服务的,需不需要监听socket呢?
// service(listensock_, client_ip, client_port);
// close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
// exit(0);//子进程退出会造成僵尸进程
// }
// close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
// //父进程
// //waitpid是阻塞式等待,也会占用资源无意义,所以这里用signal
// 2.1多进程版本,不用signal
// pid_t id = fork();
// if (id == 0)
// {
// if (fork() > 0)//这是因为我们让子进程退出之间再次fork一下,会留下一个孙子进程,孙子进程变成了孤儿进程
// //会被OS领养,会被OS自己退出,所以父进程等待是不会阻塞的!
// {
// close(listensock_);
// exit(0);
// service(servicesock, client_ip, client_port);
// exit(0);
// }
// }
// // 父进程
// waitpid(id, nullptr, 0);
// close(servicesock);
}
}
~TcpServer()
{
}
private:
uint16_t port_;
std::string ip_;
int listensock_;
};
五、线程池版本
代码自取