TCP依舊使用代碼來熟悉對應的套接字,很多接口都是在udp中使用過的
所以就不會單獨把他們拿出來作為標題了,只會把第一次出現(xiàn)的接口作為標題

通過TCP的套接字 ,來把數(shù)據(jù)交付給對方的應用層,完成雙方進程的通信
服務端 tcp_server
tcpserver.hpp(封裝)
在 tcpServer.hpp 中,創(chuàng)建一個命名空間 yzq 用于封裝
在命名空間中,定義一個類 TcpServer
該類中包含 構(gòu)造 析構(gòu) 初始化(initServer) 啟動(start)
初始化 initServer
1. 創(chuàng)建socket

設置監(jiān)聽端口號(后面會解釋) ,需要端口號標識進程的唯一性

在類外設置一個默認端口號8888作為構(gòu)造函數(shù)參數(shù)port的缺省值

創(chuàng)建套接字
輸入 man socket

第一個參數(shù) domain ,用于區(qū)分 進行網(wǎng)絡通信還是 本地通信
若想為網(wǎng)絡通信,則使用 AF_INET
若想為本地通信,則使用 AF_UNIX
第二個參數(shù) type, 套接字對應的服務類型

SOCK_STREAM 流式套接
SOCK_DGRAM 無連接不可靠的通信(用戶數(shù)據(jù)報)
第三個參數(shù) protocol ,表示想用那種協(xié)議,協(xié)議默認為0
若為 流式套接,則系統(tǒng)會認為是TCP協(xié)議 ,若為用戶數(shù)據(jù)報,則系統(tǒng)會認為是UDP協(xié)議
套接字的返回值:若成功則返回文件描述符,若失敗則返回 -1

說明進行網(wǎng)絡通信,流式套接,同時系統(tǒng)認為是TCP協(xié)議

創(chuàng)建err.hpp 用于存儲錯誤信息的枚舉

如果創(chuàng)建失敗,則終止程序
2. 綁定 bind
輸入 man 2 bind ,查看綁定

給一個套接字綁定一個名字
第一個參數(shù) sockfd 為 套接字
第二個參數(shù) addr 為 通用結(jié)構(gòu)體類型
第三個參數(shù) addrlen 為 第二個參數(shù)的實際長度大小
bind返回值:若成功,則返回0,若失敗,返回 -1

使用bind,是需要借助一個通用結(jié)構(gòu)體來實現(xiàn)的
所以定義一個 網(wǎng)絡通信類型的結(jié)構(gòu)體 local
在上一篇博客中,詳細講述了 sockaddr_in 結(jié)構(gòu)體的內(nèi)部組成
不懂可以去看看:struct sockaddr_in 的理解
htons —— 主機序列轉(zhuǎn)化為網(wǎng)絡序列
輸入 man htons ,表示短整數(shù)的主機轉(zhuǎn)網(wǎng)絡序列

所以需要將主機的port_進行轉(zhuǎn)化 ,然后再交給 local的sin_port (端口號)

INADDR_ANY 表示bind的任意IP

如果綁定失敗返回-1
3.監(jiān)聽
listen ——設為 監(jiān)聽狀態(tài)
輸入 man 2 listen
設置當前套接字狀態(tài)為 監(jiān)聽狀態(tài)

第一個參數(shù) sockfd 為 套接字
第二個參數(shù) 暫不做解釋,一般設為整數(shù)
若成功則返回0,若失敗返回-1

監(jiān)聽失敗 返回-1,并終止程序

在類外設置一個 默認整數(shù) 為32
啟動 Start

設置一個布爾變量 quit_,若為true則表示 服務器啟動, 若為false,則表示 服務器沒有啟動
如果服務器沒有啟動,則進入while循環(huán)

1.獲取連接,accept
accept
輸入 man 2 accept

需要知道誰連的你,所以要獲取到客戶端的相關信息
第一個參數(shù) sockfd 為套接字
第二個參數(shù) addr 為通用結(jié)構(gòu)體類型的 結(jié)構(gòu)體 這個結(jié)構(gòu)體是用來記錄客戶端內(nèi)的port號以及IP地址 、16位地址類型等信息
第三個參數(shù) addrlen 為 結(jié)構(gòu)體的大小
返回值:
若成功,則返回一個合法的整數(shù) 即文件描述符
若失敗,返回-1并且設置錯誤碼
accept返回的文件描述符 與 socket設置成功返回的文件描述符的關系
如:有一個魚莊,生意不太好,所以在外面站著一個人叫張三,進行攬客
有一天你和你的朋友在外面遇見張三,張三就向你們說他們魚莊有多少,推薦去他們哪里吃魚
正好你們倆也餓了,所以就跟張三去魚莊吃魚,但是只有你們進入魚莊了,張三并沒有進去
張三只是向里面喊了一聲,來客人了,然后繼續(xù)找人去了
這個時候來了一個服務員李四,向你們詢問要吃什么,并向你們提供各種服務
每一次張三把客人招呼到魚莊時,都會有一名服務員給客人提供服務
當張三做完自己的工作后,立馬返回自己的工作崗位,繼續(xù)招攬客人
張三不給用戶提供具體的服務,只負責把客人從路上拉到店里去吃飯 進行消費
李四來給客人提供服務
魚莊 可以看作是 整個服務器
像張三這樣把客人從外部 拉到餐廳里的 稱為 監(jiān)聽套接字 即accept的第一個參數(shù) sockfd
像李四這樣作的動作,相當于accept會返回一個文件描述符,這個文件描述符 是真正給用戶提供IO服務的
若張三繼續(xù)拉客,在路上碰見一個人,問他要不要去魚莊吃飯,但那個人搖了搖頭,表示沒有意愿去魚莊吃飯,
此時張三就被拒絕了,但這并不影響張三繼續(xù)拉客去魚莊
所以 accept 獲取失敗,只需繼續(xù) 執(zhí)行即可

2.獲取新連接成功,開始進行業(yè)務處理
提供一個service的函數(shù) ,參數(shù)為新的文件描述符sock
用于實現(xiàn)基本的讀寫服務 即 客戶端發(fā)消息,需要把消息轉(zhuǎn)回去
TCP 是一種流式服務
輸入 man 2 read

從文件描述符fd中將我們想要的數(shù)據(jù),按照數(shù)據(jù)塊的方式讀取出來
返回值代表多少字節(jié),讀取到文件結(jié)尾為0,失敗為-1

將sock中的數(shù)據(jù)讀取到buffer緩沖區(qū)中
若讀取成功,則將最后一位的下一位賦值為0

若read的返回值為0,則對方將連接關閉了,所以sock也可以關閉

若返回值小于0,則讀取失敗,返回錯誤碼
收到消息,需要把消息做某種處理后,再把消息轉(zhuǎn)回去
所以使用 包裝器 functional處理

在類外設置一個函數(shù)類型,返回值為string,參數(shù)為 string 的包裝器

用該函數(shù)類型定義為一個私有變量func

將處理完的消息進行返回
輸入 man 2 write
向一個文件中寫入信息

fd代表文件描述符
buf代表 緩沖區(qū)
count代表 緩沖區(qū)大小
write將緩沖區(qū)的count大小的數(shù)據(jù)寫入 fd中

將res中的數(shù)據(jù) 寫入 sock文件描述符中
tcpserver.cc (主函數(shù)main實現(xiàn))
想要只輸入 ./tcp_server 加 端口號
所以在main函數(shù)中添加命令行參數(shù)
main函數(shù)的兩個參數(shù),char* argv[] 為指針數(shù)組 ,argv為一張表,包含一個個指針,指針指向字符串
int argc,argc為數(shù)組的元素個數(shù)

當參數(shù)輸入不為2時,就會終止程序,同時打印出對應的輸入?yún)?shù)

通過構(gòu)造函數(shù)了解, 想要使用 new TcpServer 需要傳入回調(diào)和端口號

客戶端 tcp_client
tcpclient.cc(不封裝,直接實現(xiàn))
為了使用客戶端,所以要輸入對應的 可執(zhí)行程序 serverip serverport
所以在main函數(shù)需要使用 命令行參數(shù)

若輸入的參數(shù)少于3個,則終止程序,并打印出對應輸入的參數(shù)

將輸入的第二個參數(shù)的IP地址 賦值給 serverip
將輸入的第三個參數(shù)的端口號,使用atoi將字符串轉(zhuǎn)化為整數(shù) ,再賦值給serverport
1.創(chuàng)建套接字

網(wǎng)絡通信,并為流式套接,默認為0,因為流式所以為TCP協(xié)議
若創(chuàng)建套接字失敗,則終止程序
2.發(fā)起鏈接
輸入 man connect

客戶端 通過套接字sockfd,向特定的服務器發(fā)起鏈接請求
sockfd:套接字
addr:公共類型的結(jié)構(gòu)體 內(nèi)部包含 服務器的IP地址和的端口號
addrlen:結(jié)構(gòu)體的大小
返回值:若成功,則返回0,若失敗,返回-1和錯誤碼
首次發(fā)起鏈接時,操作系統(tǒng)會給客戶端自動進行綁定端口
所以需要先定義一個結(jié)構(gòu)體server

借助htons 將上述的主機序列端口號serverport 轉(zhuǎn)化為網(wǎng)絡序列端口號
inet_addr——字符串IP地址 轉(zhuǎn)為 網(wǎng)絡序列IP地址
輸入man inet_addr

第一個參數(shù)為 字符串風格的IP地址
第二個參數(shù) 為 網(wǎng)絡序列的IP地址
將 字符串風格的IP地址 轉(zhuǎn)為 網(wǎng)絡序列的IP地址

再將主機序列的IP地址serverip,轉(zhuǎn)化為網(wǎng)絡序列的IP地址

cnt表示重連次數(shù)
設置while循環(huán),當不等于0鏈接失敗時,cnt值減1,并重新鏈接,若cnt值為0,則break終止循環(huán)
若出了while循環(huán),cont小于等于0,則終止程序文章來源:http://www.zghlxwxcb.cn/news/detail-643396.html
3. 鏈接成功

創(chuàng)建一個string類型的line,將輸入的參數(shù)傳入line中
使用write,將line的內(nèi)容傳入文件描述符中
使用read,將sock的數(shù)據(jù)傳入buffer中
通過read的返回值來判斷,若返回值大于0則,輸出其中內(nèi)容
若返回值等于0,則說明鏈接關閉,則退出while循環(huán)
若返回值小于,則說明創(chuàng)建失敗,返回錯誤碼文章來源地址http://www.zghlxwxcb.cn/news/detail-643396.html
具體代碼實現(xiàn)
err.hpp(用于存放錯誤信息)
#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( 服務端 封裝)
#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;//默認整數(shù)為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)//構(gòu)造
: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.創(chuàng)建socket
listensock_=socket(AF_INET,SOCK_STREAM,0);
if(listensock_<0)//創(chuàng)建失敗
{
std::cout<<" create socket errno"<<std::endl;
exit(SOCKET_ERR);//終止程序
}
//2. bind 綁定
struct sockaddr_in local;//網(wǎng)絡通信類型
//清空
memset(&local,'\0',sizeof(local));
local.sin_family=AF_INET;//網(wǎng)絡通信
//htons 主機轉(zhuǎn)網(wǎng)絡
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.監(jiān)聽
if(listen(listensock_,backlog)<0)
{
//監(jiān)聽失敗返回-1
std::cout<<" listen socket errno"<<std::endl;
exit(LISTEN_ERR);//終止程序
}
}
void start()//啟動
{
quit_=false;//服務器沒有啟動
while(!quit_)
{
//4.獲取連接,accept
struct sockaddr_in client;//網(wǎng)絡通信類型
socklen_t len=sizeof(client);//結(jié)構(gòu)體大小
int sock=accept(listensock_,(struct sockaddr*)&client,&len);
if(sock<0)
{
//獲取失敗
std::cout<<" accept errno"<<std::endl;
continue;//繼續(xù)執(zhí)行
}
//提取客戶端信息
std::string clientip=inet_ntoa(client.sin_addr);//客戶端ip
uint16_t clientport=ntohs(client.sin_port);//客戶端端口號
//5.獲取新連接成功,開始進行業(yè)務處理
std::cout<<"獲取新連接成功: "<<sock<<"from "<<listensock_<<std::endl;
//service(sock);//多線程版本沒有調(diào)用函數(shù)
//多線程版本
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中的數(shù)據(jù)讀取到buffer中
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
//讀取成功
buffer[s]=0;
//使用func 進行回調(diào)
std::string res=func_(buffer);
std::cout<<res<<std::endl;
//將res中的數(shù)據(jù)寫給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_;//函數(shù)類型
int listensock_;//監(jiān)聽套接字
bool quit_;//表示服務器是否啟動
uint16_t port_;//端口號
};
}
tcpServer.cc( 服務端 主函數(shù)實現(xiàn))
#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[])
{
//輸入兩個參數(shù) 所以不等于2
if(argc!=2)
{
usage(argv[0]);
exit(USAGE_ERR);//終止程序
}
//將輸入的端口號 轉(zhuǎn)化為整數(shù)
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.創(chuàng)建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
//創(chuàng)建失敗
cout<<"socket errnr:"<<strerror(errno)<<endl;
exit(SOCKET_ERR);//終止程序
}
//2.發(fā)起鏈接
struct sockaddr_in server;
memset(&server,0,sizeof(server));//清空
server.sin_family=AF_INET;//網(wǎng)絡通信類型
//htons 主機序列轉(zhuǎn)為網(wǎng)絡序列
server.sin_port=htons(serverport);//網(wǎng)絡端口號
inet_aton(serverip.c_str(),&server.sin_addr);//網(wǎng)絡IP地址
int cnt=5;//重連次數(shù)
while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)
{
//不等于0則鏈接失敗
sleep(1);
cout<<"正在嘗試重連,重連次數(shù)還有:"<<cnt--<<endl;
if(cnt<=0)
{
//沒有重連次數(shù)
break;
}
}
if(cnt<=0)
{
//鏈接失敗
cout<<"鏈接失敗.."<<endl;
exit(SOCKET_ERR);//終止程序
}
char buffer[1024];
//3.鏈接成功
while(true)
{
string line;
cout<<"enter>>";
getline(cin,line);//從cin中獲取內(nèi)容 寫入line中
write(sock,line.c_str(),line.size());//將line中的內(nèi)容寫入到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;
}
到了這里,關于【網(wǎng)絡通信】socket編程——TCP套接字的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!