🌏博客主页:PH_modest的博客主页
🚩当前专栏:Linux跬步积累
💌其他专栏:
🔴 每日一题
🟡 C++跬步积累
🟢 C语言跬步积累
🌈座右铭:广积粮,缓称王!
文章目录
一、TCP socket API
下面介绍程序中用到的socket API,这些函数都在sys/socket.h中。
//创建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);
二、TCP API 使用
1、服务端创建套接字
函数原型:
//创建socket文件描述符(TCP/UDP,客户端 + 服务器)
int socket(int domain,int type,int protocol);
使用示例:
//创建文件描述符
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock < 0)
{
std::cerr<<"socket error!"<<std::endl;
exit(1);
}
2、服务端绑定
函数原型:
//绑定端口号(TCP/UDP,服务器)
int bind(int socket,const struct sockaddr *address,socklen_t address_len);
使用示例:
//绑定
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)
{
std::cerr<<"bind error!"<<std::endl;
exit(2);
}
3、服务端监听
函数原型:
//开始监听socket(TCP,服务器)
int listen(int socket,int backlog);
使用示例:
//监听
if(listen(_listensock,5) < 0)
{
std::cerr<<"listen error!"<<std::endl;
exit(3);
}
4、服务端获取连接
函数原型:
//接收请求(TCP,服务器)
int accept(int socket,struct sockaddr* address,socklen_t* address_len);
参数说明:
- socket:特定的监听套接字,表示从这个套接字获取连接。
- address:对端网络相关信息,包括协议家族、IP地址、端口号。
- address_len:这是一个输入输出型参数,调用时传入期望读取的长度,返回时表示实际读取的长度。
accept函数返回的套接字是什么?和socket有什么区别?
accept函数获取连接时,是从socket监听套接字当中获取的,如果accept获取连接成功,此时就会返回接收到的套接字对应的文件描述符。
socket监听套接字的作用是用来获取客户端发来的新的连接请求。accept会不断从监听套接字当中获取新连接。
accept返回的套接字是为本次获取到的连接提供服务的。监听套接字是不断获取新的连接,真正为这些连接提供服务的是accept返回的套接字,而不是监听套接字。
监听套接字可以看成饭店门口拉客的员工,当你被她说服进店之后,会有新的服务员单独为你提供服务,而这个新的服务员就是accept返回的套接字。
使用示例:
void Start()
{
while(true)
{
struct sockaddr_in peer;
memset(&peer,0,sizeof(peer));
socklen_t len = sizeof(peer);
int sockfd = accept(_listensock,(struct sockaddr*)&peer,&len);
if(sockfd < 0)
{
std::cerr<<"accept error!"<<std::endl;
continue;//这里不能直接退出,因为还需要获取其他连接
}
std::string client_ip = inet_ntoa(peer.sin_addr);//将网络序列转换为主机序列,同时将整数ip变为字符串ip
int client_port = ntohs(peer.sin_port);//将网络序列转换为主机序列
std::cout<<"get a new link-->"<<sockfd<<"["<<client_ip<<"]:"<<client_port<<std::endl;
}
}
5、服务端接收连接测试
我们现在做一个简单的测试,测试一下当前服务器能否成功接受请求连接。
void Usage(char *proc)
{
std::cout<<"Usage:\n\t"<<proc<<" local_port\n";
}
//./tcpserver port
int main(int argc,char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
int port = std::stoi(argv[1]);
TcpServer* tsvr = new TcpServer(port);
tsvr->InitServer();
tsvr->Start();
return 0;
}
我们编译运行之后,可以通过netstat
命令来显示网络连接、路由表、接口统计等网络相关信息。
echo server
多进程版本
TcpServer.hpp
#pragma once
#include"TcpServer.hpp"
#include<iostream>
#include<string>
#include<strings.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include"InetAddr.hpp"
static const int gbacklog = 15;
class TcpServer
{
public:
TcpServer(uint16_t port):_port(port),_isrunning(false)
{
}
void InitServer()
{
//1. 创建流式套接字
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock < 0)
{
std::cerr<<"socket error!\n";
exit(1);
}
std::cout<<"socket success,sockfd is:"<<_listensock<<std::endl;
//2. bind
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
int n = ::bind(_listensock,(struct sockaddr*)&local,sizeof(local));
if(n < 0)
{
std::cerr<<"bind error!\n";
exit(2);
}
std::cout<<"bind success,sockfd is:"<<_listensock<<std::endl;
//3. tcp是面向连接的,所以通信之前,必须先建立连接,服务器是被连接的
// tcpserver启动,未来首先要一直等待客户的连接到来
n = listen(_listensock,gbacklog);
if(n < 0)
{
std::cerr<<"listen error!\n";
exit(3);
}
std::cout<<"listen success,sockfd is:"<<_listensock<<std::endl;
}
void Service(int sockfd,InetAddr client)
{
printf("get a new link,info %s:%d,fd:%d\n",client.IP().c_str(),client.Port(),sockfd);
std::string clientaddr = "["+client.IP()+":"+std::to_string(client.Port())+"]# ";
while(true)
{
//读取数据
char inbuffer[1024];
ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);
if(n > 0)//回写
{
inbuffer[n] = 0;
std::cout<<clientaddr<<inbuffer<<std::endl;
std::string echo_string = "[server echo]# ";
echo_string+=inbuffer;
write(sockfd,echo_string.c_str(),echo_string.size());
}
else if(n == 0)//退出
{
//client 退出&&关闭链接了
std::cout<<clientaddr<<" quit!\n";
break;
}
else//报错
{
std::cerr<<clientaddr<<"read error!\n";
break;
}
}
//一定要关闭,因为文件描述符表是一个数组,数组容量是有限的
close(sockfd);