TCP still uses codes to get familiar with the corresponding sockets. Many interfaces have been used in udp,
so they will not be taken out separately as the title, and only the interface that appears for the first time will be used as the title.
Article directory
Through the TCP socket, the data is delivered to the application layer of the other party, and the communication between the two processes is completed.
server tcp_server
tcpserver.hpp (encapsulation)
In tcpServer.hpp, create a namespace yzq for encapsulation
In the namespace, define a class TcpServer
This class contains construction, destruction, initialization (initServer) startup (start)
Initialize initServer
1. Create a socket
Set the listening port number (explained later), the port number is required to identify the uniqueness of the process
Set a default port number 8888 outside the class as the default value of the constructor parameter port
create socket
Enter man socket
The first parameter domain is used to distinguish between network communication and local communication.
If you want to communicate over the network, use AF_INET;
if you want to communicate locally, use AF_UNIX
The second parameter type, the service type corresponding to the socket
SOCK_STREAM stream socket
SOCK_DGRAM connectionless unreliable communication (user datagram)
The third parameter, protocol, indicates which protocol you want to use. The default protocol is 0.
If it is a stream socket, the system will consider it to be the TCP protocol. If it is a user datagram, the system will consider it to be the UDP protocol.
The return value of the socket: the file descriptor is returned if successful, and -1 is returned if it fails
Indicates network communication, stream socket, and the system considers it to be TCP protocol
Create an enumeration of err.hpp to store error information
If creation fails, terminate the program
2. bind bind
Enter man 2 bind to view the binding
Bind a name to a socket
The first parameter sockfd is the socket
The second parameter addr is the general structure type
The third parameter addrlen is the actual length of the second parameter
bind return value: if successful, return 0, if failed, return -1
The use of bind needs to be realized with the help of a general structure,
so define a structure of network communication type local
In the last blog, I described the internal composition of the sockaddr_in structure in detail.
If you don’t understand, you can go to see: Understanding of struct sockaddr_in
htons - convert host sequence to network sequence
Enter man htons, which represents the host-to-network sequence of short integers
So you need to convert the port_ of the host, and then hand it over to the local sin_port (port number)
INADDR_ANY means any IP of bind
Returns -1 if binding fails
3. Monitor
listen - set to listening state
Enter man 2 listen
to set the current socket state to the listening state
The first parameter sockfd is the socket.
The second parameter is not explained temporarily, and it is generally set to an integer.
If successful, it returns 0, and if it fails, it returns -1.
If the monitoring fails, return -1 and terminate the program
Set a default integer to 32 outside the class
start
Set a Boolean variable quit_, if it is true, it means that the server is started, if it is false, it means that the server is not started
If the server is not started, enter a while loop
1. Get connection, accept
accept
Type man 2 accept
You need to know who is connected to you, so you need to get relevant information about the client
The first parameter sockfd is the socket,
the second parameter addr is a structure of general structure type. This structure is used to record the port number, IP address, 16-bit address type and other information in the client. The third parameter
addrlen is the size of the structure
Return value:
If successful, return a legal integer that is the file descriptor
; if failed, return -1 and set the error code
The relationship between the file descriptor returned by accept and the file descriptor successfully returned by socket setting
For example: There is a fish shop, the business is not very good, so there is a man named Zhang San standing outside, to attract customers.
One day you and your friends meet Zhang San outside, and Zhang San tells you how many fish farms they have. , I recommend going to where they eat fish.
You two are also hungry, so I went to Yuzhuang to eat fish with Zhang San, but only you entered the fish village, Zhang San did not go in, Zhang San
just shouted inside, there are guests , and then continued to find someone.
At this time, a waiter Li Si came to ask you what you want to eat and provide you with various services
Every time Zhang San welcomes guests to Yuzhuang, there will be a waiter to provide service to the guests.
When Zhang San finishes his work, he immediately returns to his job and continues to attract guests
Zhang San does not provide specific services to users, but is only responsible for pulling
customers from the road to the restaurant for consumption. The sockfd, which is called the listening socket in the first parameter of accept , acts like Li Si, which is equivalent to accept returning a file descriptor. This file descriptor is really providing IO services to users.
If Zhang San continues to solicit customers, he meets a person on the road and asks him if he wants to go to Yuzhuang for dinner, but the person shakes his head and expresses that he does not want to go to Yuzhuang for dinner. At this time,
Zhang San will be rejected, but this does not affect Zhang San continued to solicit customers to go to Yuzhuang
, so the accept failed, just continue to execute
2. Obtain a new connection successfully and start business processing
Provide a service function, the parameter is the new file descriptor sock
used to implement basic read and write services, that is, the client sends a message, and the message needs to be transferred back
TCP is a streaming service
enter man 2 read
Read the data we want from the file descriptor fd in the form of data blocks.
How many bytes the return value represents. When the end of the file is read, it is 0, and if it fails, it is -1.
Read the data in the sock into the buffer buffer
If the reading is successful, assign the next bit of the last bit to 0
If the return value of read is 0, the other party will close the connection, so the sock can also be closed
If the return value is less than 0, the reading fails and an error code is returned
After receiving the message, you need to do some processing on the message, and then transfer the message back,
so use the wrapper functional processing
Set a function type outside the class, the return value is string, and the parameter is a wrapper of string
Define the function type as a private variable func
Return the processed message
Enter man 2 write
to write information to a file
fd represents the file descriptor
buf represents the buffer
count represents the buffer size
write writes the data of the count size of the buffer into fd
Write the data in res to the sock file descriptor
tcpserver.cc (main function main implementation)
I want to just enter ./tcp_server plus the port number , so add two parameters of
the command line parameter main function to the main function , char* argv[] is an array of pointers, and argv is a table containing pointers, which point to strings int argc, argc is the number of elements in the array
When the parameter input is not 2, the program will be terminated and the corresponding input parameters will be printed out at the same time
Knowing through the constructor, if you want to use new TcpServer, you need to pass in the callback and port number
client tcp_client
tcpclient.cc (not encapsulated, directly implemented)
In order to use the client, you need to enter the corresponding executable program serverip serverport
so you need to use command line parameters in the main function
If the input parameters are less than 3, terminate the program and print out the corresponding input parameters
Assign the IP address of the second parameter input to
the port number of the third parameter input by serverip, use atoi to convert the string into an integer, and then assign it to serverport
1. Create a socket
Network communication, and it is a stream socket, the default is 0, because it is a stream, so it is a TCP protocol
. If the socket creation fails, the program will be terminated
2. Initiate link
Type man accept
The client initiates a connection request to a specific server through the socket sockfd
sockfd: socket
addr: the public type structure contains the server's IP address and port number
addrlen: the size of the structure
Return value: If successful, it will return 0, if it fails, it will return -1 and an error code
When the connection is initiated for the first time, the operating system will automatically bind the port to the client
So you need to define a structure server first
Use htons to convert the above-mentioned host serial port number serverport into a network serial port number
inet_addr - string IP address to network sequence IP address
Enter man inet_addr
The first parameter is the IP address of the string style,
and the second parameter is the IP address of the network sequence
Convert the IP address of the string style to the IP address of the network sequence
Then convert the IP address serverip of the host sequence into the IP address of the network sequence
cnt indicates the number of reconnections
. Set the while loop. When the connection fails when it is not equal to 0, the cnt value will be reduced by 1 and the connection will be reconnected. If the cnt value is 0, break will terminate the loop. If the while loop exits and cont is less than or
equal to 0, the program will be terminated.
3. The link is successful
Create a line of string type, pass the input parameters into the line
use write, pass the content of the line into the file descriptor
use read, pass the sock data into the buffer
and judge by the return value of read, if the return value If it is greater than 0, output the content
. If the return value is equal to 0, it means that the link is closed, and then exit the while loop.
If the return value is less than, it means that the creation failed and an error code is returned.
Specific code implementation
err.hpp (used to store error messages)
#pragma once
enum
{
USAGE_ERR=1,
SOCKET_ERR,//2
BIND_ERR,//3
LISTEN_ERR//4
};
makefile
.PHONY:all
all: tcp_client tcp_server
tcp_client:tcpClient.cc
g++ -o $@ $^ -std=c++11 -lpthread
tcp_server:tcpServer.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f tcp_client tcp_server
tcpServer.hpp (server package)
#pragma once
#include<iostream>
#include<cstdlib>
#include<string.h>
#include<unistd.h>
#include"err.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<functional>
namespace yzq
{
static uint16_t defaultport=8888;//默认端口号
static const int backlog=32;//默认整数为32
using func_t=std::function<std::string(const std::string&)>;
class TcpServer;
class ThreadData//该类用于存放客户端的IP port 套接字
{
public:
ThreadData(int fd,const std::string&ip,const uint16_t &port,TcpServer*ts)//构造
:sock(fd),clientip(ip),clientport(port),current(ts)
{
}
public:
int sock;//套接字
std::string clientip;//客户端IP
uint16_t clientport;//客户端端口号
TcpServer*current;
};
class TcpServer
{
public:
TcpServer(func_t func,uint16_t port=defaultport)
:func_(func),port_(port),quit_(true)//表示默认启动
{
}
void initServer()//初始化
{
//1.创建socket
listensock_=socket(AF_INET,SOCK_STREAM,0);
if(listensock_<0)//创建失败
{
std::cout<<" create socket errno"<<std::endl;
exit(SOCKET_ERR);//终止程序
}
//2. bind 绑定
struct sockaddr_in local;//网络通信类型
//清空
memset(&local,'\0',sizeof(local));
local.sin_family=AF_INET;//网络通信
//htons 主机转网络
local.sin_port=htons(port_);//端口号
local.sin_addr.s_addr=INADDR_ANY ; //IP地址
if(bind(listensock_,(struct sockaddr*)&local,sizeof(local))<0)
//失败返回-1
{
std::cout<<" bind socket errno"<<std::endl;
exit(BIND_ERR);//终止程序
}
// 3.监听
if(listen(listensock_,backlog)<0)
{
//监听失败返回-1
std::cout<<" listen socket errno"<<std::endl;
exit(LISTEN_ERR);//终止程序
}
}
void start()//启动
{
quit_=false;//服务器没有启动
while(!quit_)
{
//4.获取连接,accept
struct sockaddr_in client;//网络通信类型
socklen_t len=sizeof(client);//结构体大小
int sock=accept(listensock_,(struct sockaddr*)&client,&len);
if(sock<0)
{
//获取失败
std::cout<<" accept errno"<<std::endl;
continue;//继续执行
}
//提取客户端信息
std::string clientip=inet_ntoa(client.sin_addr);//客户端ip
uint16_t clientport=ntohs(client.sin_port);//客户端端口号
//5.获取新连接成功,开始进行业务处理
std::cout<<"获取新连接成功: "<<sock<<"from "<<listensock_<<std::endl;
//service(sock);//多线程版本没有调用函数
//多线程版本
pthread_t tid;
ThreadData*td=new ThreadData(sock,clientip,clientport,this);
pthread_create(&tid,nullptr,threadRoutine,td);
}
}
static void *threadRoutine(void*args)
{
pthread_detach(pthread_self());//线程分离
ThreadData*td=(ThreadData*)args;
td->current->service(td->sock);
delete td;
return nullptr;
}
void service(int sock)
{
char buffer[1024];
while(true)
{
//将sock中的数据读取到buffer中
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
//读取成功
buffer[s]=0;
//使用func 进行回调
std::string res=func_(buffer);
std::cout<<res<<std::endl;
//将res中的数据写给sock中
write(sock,res.c_str(),res.size());
}
else if(s==0)
{
//说明对方将连接关闭了
close(sock);
std::cout<<"client quit,me too"<<std::endl;
break;
}
else
{
//读取失败返回-1
std::cout<<"read errno"<<strerror(errno)<<std::endl;
break;
}
}
}
~TcpServer()
{
}
private:
func_t func_;//函数类型
int listensock_;//监听套接字
bool quit_;//表示服务器是否启动
uint16_t port_;//端口号
};
}
tcpServer.cc (server main function implementation)
#include"tcpServer.hpp"
#include<memory>//智能指针
using namespace std;
using namespace yzq;
static void usage(string proc)
{
std::cout<<"usage:\n\t"<<proc<<"port\n"<<std::endl;
}
std::string echo(const std::string&message)
{
return message;
}
// ./tcp_server port
int main(int argc,char*argv[])
{
//输入两个参数 所以不等于2
if(argc!=2)
{
usage(argv[0]);
exit(USAGE_ERR);//终止程序
}
//将输入的端口号 转化为整数
uint16_t port=atoi(argv[1]);
unique_ptr<TcpServer>tsvr(new TcpServer(echo,port));
tsvr->initServer();//服务器初始化
tsvr->start();//启动
return 0;
}
tcpClient.cc (the client is not encapsulated)
#include<iostream>
#include<cstring>
#include<unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"err.hpp"
using namespace std;
static void usage(string proc)
{
std::cout<<"usage:\n\t"<<proc<<"port\n"<<std::endl;
}
//./tcp_client serverip serverport
int main(int argc,char*argv[])
{
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);//终止程序
}
std::string serverip=argv[1];//IP地址
uint16_t serverport=atoi(argv[2]);//端口号
//1.创建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
//创建失败
cout<<"socket errnr:"<<strerror(errno)<<endl;
exit(SOCKET_ERR);//终止程序
}
//2.发起链接
struct sockaddr_in server;
memset(&server,0,sizeof(server));//清空
server.sin_family=AF_INET;//网络通信类型
//htons 主机序列转为网络序列
server.sin_port=htons(serverport);//网络端口号
inet_aton(serverip.c_str(),&server.sin_addr);//网络IP地址
int cnt=5;//重连次数
while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)
{
//不等于0则链接失败
sleep(1);
cout<<"正在尝试重连,重连次数还有:"<<cnt--<<endl;
if(cnt<=0)
{
//没有重连次数
break;
}
}
if(cnt<=0)
{
//链接失败
cout<<"链接失败.."<<endl;
exit(SOCKET_ERR);//终止程序
}
char buffer[1024];
//3.链接成功
while(true)
{
string line;
cout<<"enter>>";
getline(cin,line);//从cin中获取内容 写入line中
write(sock,line.c_str(),line.size());//将line中的内容写入到sock文件描述符中
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
cout<<"server echo"<<buffer<<endl;
}
else if(s==0)
{
cout<<"server quit"<<endl;
break;
}
else
{
cout<<"read errno"<<strerror(errno)<<endl;
break;
}
}
close(sock);
return 0;
}