TCP は依然として対応するソケットを把握するためにコードを使用しますが、udp では多くのインターフェイスが使用されているため、
タイトルとして個別に取り出すことはせず、初めて登場するインターフェイスのみがタイトルとして使用されます。
記事ディレクトリ
TCPソケットを介して相手のアプリケーション層にデータが届けられ、両プロセス間の通信が完了します。
サーバー tcp_server
tcpserver.hpp (カプセル化)
tcpServer.hpp で、カプセル化用の名前空間 yzq を作成します
。 名前空間で、クラス TcpServer を定義します。
このクラスには構築、破壊、初期化(initServer)、起動(start)が含まれます。
initServerの初期化
1. ソケットを作成する
リスニングポート番号を設定します(後述)。ポート番号はプロセスの一意性を識別するために必要です。
コンストラクターパラメータ port のデフォルト値として、クラス外のデフォルトのポート番号 8888 を設定します。
ソケットを作成する
男性ソケットに入る
最初のパラメータのドメインは、ネットワーク通信とローカル通信を区別するために使用されます。
ネットワーク経由で通信する場合は AF_INET を使用し、
ローカルで通信する場合は AF_UNIX を使用します。
2 番目のパラメータのタイプ、ソケットに対応するサービスのタイプ
SOCK_STREAM ストリーム ソケット
SOCK_DGRAM コネクションレスで信頼性の低い通信 (ユーザー データグラム)
3 番目のパラメータ、protocol は、使用するプロトコルを示します。デフォルトのプロトコルは 0 です。ストリーム
ソケットの場合、システムはそれを TCP プロトコルとみなします。ユーザー データグラムの場合、システムはそれを TCP プロトコルとみなします。 UDPプロトコルになります。
ソケットの戻り値: 成功した場合はファイル記述子が返され、失敗した場合は -1 が返されます。
ネットワーク通信、ストリームソケットを示し、システムはそれを TCP プロトコルと見なします。
エラー情報を保存するための err.hpp の列挙を作成します。
作成に失敗した場合はプログラムを終了する
2.バインドバインド
man 2 binding と入力してバインディングを表示します
名前をソケットにバインドします
最初のパラメータ sockfd はソケットです
2 番目のパラメータ addr は一般構造タイプです
3 番目のパラメータ addrlen は 2 番目のパラメータの実際の長さです
バインドの戻り値: 成功した場合は 0 を返し、失敗した場合は -1 を返します。
バインドの使用は一般的な構造を利用して実現する必要があるため、
ネットワーク通信タイプのローカルの構造を定義します。
前回のブログではsockaddr_in構造体の内部構成を詳しく説明しましたが、分からない方は「 struct sockaddr_inの理解」
をご覧ください。
htons - ホストシーケンスをネットワークシーケンスに変換します
man htonsを入力します。これは、ホストからネットワークへの短い整数のシーケンスを表します。
したがって、ホストの port_ を変換してから、それをローカルの sin_port (ポート番号) に渡す必要があります。
INADDR_ANY はバインドの任意の IP を意味します
バインディングが失敗した場合は -1 を返します
3. モニター
listen - リスニング状態に設定します
man 2 listenと入力して、現在のソケット状態を listen 状態に設定します。
第一引数 sockfd はソケット、
第二引数は一時的に説明しませんが、通常は整数が設定され、
成功すれば 0 を返し、失敗すれば -1 を返します。
監視に失敗した場合は、-1 を返してプログラムを終了します。
クラス外のデフォルトの整数を 32 に設定する
始める
ブール変数 quit_ を設定します。 true の場合はサーバーが起動していることを意味し、 false の場合はサーバーが起動されていないことを意味します
サーバーが起動していない場合は、while ループに入る
1. 接続を取得し、受け入れます
受け入れる
タイプマン2受け入れ
誰が自分に接続しているのかを知る必要があるため、クライアントに関する関連情報を取得する必要があります
最初のパラメータ sockfd はソケット、
2 番目のパラメータ addr は一般構造型の構造体です。この構造体は、クライアント内のポート番号、IP アドレス、16 ビット アドレスの種類などの情報を記録するために使用されます。3 番目のパラメータ addrlen は
、構造物のサイズ
戻り値:成功した場合は、ファイル記述子
である正当な整数を返します。失敗した場合は、-1 を返し、エラー コードを設定します。
accept によって返されたファイル記述子とソケット設定によって正常に返されたファイル記述子の間の関係
例: 魚屋がありますが、経営があまり良くないので、客を呼び込むために外に立っている張三という男がいます。ある日、あなたとあなたの友達が外で張三に会い、張三は魚の数を教えてくれます
。彼らが持っている農場。魚を食べるところに行くことをお勧めします。
あなたたち二人もお腹が空いているので、張三と一緒に魚を食べに魚荘に行きましたが、あなただけが魚村に入りました、張三は入らず、張三だけでし
た中で「お客さんが来ました」と叫び、引き続き誰かを探していると
、ウェイターのLi Siが来て、何を食べたいかを尋ね、さまざまなサービスを提供します
張三が豫荘に客人を迎えるたびに、ウェイターが来客にサービスを提供し、
仕事が終わるとすぐに仕事に戻り、客を呼び込み続ける。
Zhang San はユーザーに特定のサービスを提供するのではなく、消費のために道路からレストランまで
顧客を誘導する
責任だけを負います。 accept の最初のパラメーターでリスニング ソケットと呼ばれる sockfd は、Li Si のように機能します。このファイル記述子は実際にユーザーに IO サービスを提供します。
張三が客引きを続けると、道で出会った人に夕庄に夕食を食べに行かないか尋ねますが、その人は首を振って夕庄に夕食に行きたくないと言います。時間になると、
Zhang San は拒否されますが、これは影響しません。Zhang San は顧客に Yuzhuang に行くよう勧誘し続けたため
、受け入れは失敗しました。実行を続けるだけです。
2. 新しい接続を正常に取得し、業務処理を開始します
サービス関数を提供します。パラメータは、基本的な読み取りおよび書き込みサービスを実装するために使用される新しいファイル記述子ソケットです
。つまり、クライアントがメッセージを送信し、そのメッセージを返送する必要があります。
TCP はストリーミング サービスです。
Enter man 2 read
ファイル記述子 fd から必要なデータをデータ ブロックの形式で読み取ります。
戻り値は何バイトを表します。ファイルの終わりが読み取られた場合は 0、失敗した場合は -1 になります。
ソケット内のデータをバッファバッファに読み取ります
。読み取りが成功したら、最後のビットの次のビットを 0 に割り当てます。
readの戻り値が0の場合は相手がコネクションを閉じるのでsockも閉じることができます
戻り値が 0 未満の場合、読み取りは失敗し、エラー コードが返されます。
メッセージを受信した後、メッセージに対して何らかの処理を行ってから、メッセージを返送する必要がある
ため、ラッパー関数の処理を使用します。
関数の型をクラスの外に設定し、戻り値は文字列で、パラメータは文字列のラッパーです。
関数の型をプライベート変数 func として定義します。
処理されたメッセージを返しますman 2 write と
入力して情報をファイルに書き込みます
fd はファイル記述子を表します
buf はバッファ
数を表します バッファサイズを表します
write はバッファのカウントサイズのデータを fd に書き込みます
res 内のデータを sock ファイル記述子に書き込みます
tcpserver.cc (main関数のメイン実装)
./tcp_server とポート番号を入力するだけなので、コマンドラインパラメータ main関数の 2 つのパラメータを
main 関数に追加します。char * argv[] はポインタの配列で、argv はポインタを含むテーブルです。文字列へのint argc、argc は配列内の要素の数です
パラメータ入力が 2 でない場合、プログラムは終了し、同時に対応する入力パラメータが出力されます。
コンストラクターを通じて、新しい TcpServer を使用する場合は、コールバックとポート番号を渡す必要があることがわかります。
クライアント tcp_client
tcpclient.cc (カプセル化されておらず、直接実装されています)
クライアントを使用するには、対応する実行可能プログラムserveripserverportを入力する必要があるため、
main関数でコマンドラインパラメータを使用する必要があります。
入力パラメータが 3 未満の場合は、プログラムを終了し、対応する入力パラメータを出力します。
serveripで入力した第3パラメータのポート番号に第2パラメータ入力のIPアドレスを代入し、atoiで文字列を整数に変換してserverportに代入します。
1. ソケットを作成する
ネットワーク通信、ストリーム ソケットです。ストリームなので、デフォルトは 0 です。TCP プロトコルです。
ソケットの作成に失敗した場合、プログラムは終了します。
2. リンクを開始する
タイプマンは受け入れます
クライアントは、ソケット sockfd を通じて特定のサーバーへの接続要求を開始します。 sockfd
: ソケット
addr: サーバーの IP アドレスとポート番号が含まれるパブリック型構造体
addrlen: 構造体のサイズ
戻り値: 成功した場合は 0 を返し、失敗した場合は -1 とエラー コードを返します。
初めて接続が開始されると、オペレーティング システムは自動的にポートをクライアントにバインドします。
したがって、最初に構造サーバーを定義する必要があります
htonsを使用して、上記のホストのシリアルポート番号serverportをネットワークのシリアルポート番号に変換します。
inet_addr - 文字列 IP アドレスからネットワーク シーケンス IP アドレスへ
man inet_addrを入力してください
最初のパラメータは文字列スタイルの IP アドレス、
2 番目のパラメータはネットワーク シーケンスの IP アドレスです。
文字列スタイルの IP アドレスをネットワーク シーケンスの IP アドレスに変換します。
次に、ホスト シーケンスの IP アドレス serverip をネットワーク シーケンスの IP アドレスに変換します。
cnt は再接続回数を示します
。while ループを設定します。0 以外で接続に失敗した場合、cnt 値は 1 減算され、再接続されます。cnt 値が 0 の場合、break は接続を終了します。 while ループが終了し、cont が
0 以下の場合、プログラムは終了します。
3. リンクが成功しました
文字列型の行を作成し、
write を使用してその行に入力パラメータを渡し、read を使用して行の内容をファイル記述子に渡し
、sock データをバッファに渡し
、read の戻り値で判断します。 0 より大きい場合はコンテンツを出力します
。戻り値が 0 に等しい場合はリンクが閉じられたことを意味し、while ループを終了します。
戻り値が 0 より小さい場合は作成が失敗したことを意味し、エラーコードが返されます。
特定のコードの実装
err.hpp (エラーメッセージの保存に使用)
#pragma once
enum
{
USAGE_ERR=1,
SOCKET_ERR,//2
BIND_ERR,//3
LISTEN_ERR//4
};
メイクファイル
.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 (サーバー パッケージ)
#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 (サーバーメイン機能実装)
#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 (クライアントはカプセル化されていません)
#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;
}