簡(jiǎn)介
- Socket理論
- Socket工作流程
- 核心函數(shù)講解
- 服務(wù)的如何獲取客戶端的信息
- 字符串ip和網(wǎng)絡(luò)二進(jìn)制的轉(zhuǎn)換
- 大小端問題
- 示例源代碼
Socket理論
socket起源于Unix薪缆,而Unix/Linux基本哲學(xué)之一就是“一切皆文件”宇智,都可以用“打開open –> 讀寫write/read –> 關(guān)閉close”模式來操作魂莫。Socket就是該模式的一個(gè)實(shí)現(xiàn)往踢, socket即是一種特殊的文件,一些socket函數(shù)就是對(duì)其進(jìn)行的操作(讀/寫IO漱竖、打開颊艳、關(guān)閉)。
說白了Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層哆料,它是一組接口。在設(shè)計(jì)模式中吗铐,Socket其實(shí)就是一個(gè)門面模式东亦,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對(duì)用戶來說唬渗,一組簡(jiǎn)單的接口就是全部典阵,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議谣妻。
注意:
其實(shí)socket也沒有層的概念萄喳,它只是一個(gè)facade設(shè)計(jì)模式的應(yīng)用,讓編程變的更簡(jiǎn)單蹋半。是一個(gè)軟件抽象層他巨。在網(wǎng)絡(luò)編程中,我們大量用的都是通過socket實(shí)現(xiàn)的。
套接字描述符
其實(shí)就是一個(gè)整數(shù)染突,我們最熟悉的句柄是0捻爷、1、2三個(gè)份企,0是標(biāo)準(zhǔn)輸入也榄,1是標(biāo)準(zhǔn)輸出,2是標(biāo)準(zhǔn)錯(cuò)誤輸出司志。0甜紫、1、2是整數(shù)表示的骂远,對(duì)應(yīng)的FILE *結(jié)構(gòu)的表示就是stdin囚霸、stdout、stderr
套接字API最初是作為UNIX操作系統(tǒng)的一部分而開發(fā)的激才,所以套接字API與系統(tǒng)的其他I/O設(shè)備集成在一起拓型。特別是,當(dāng)應(yīng)用程序要為因特網(wǎng)通信而創(chuàng)建一個(gè)套接字(socket)時(shí)瘸恼,操作系統(tǒng)就返回一個(gè)小整數(shù)作為描述符(descriptor)來標(biāo)識(shí)這個(gè)套接字劣挫。然后,應(yīng)用程序以該描述符作為傳遞參數(shù)东帅,通過調(diào)用函數(shù)來完成某種操作(例如通過網(wǎng)絡(luò)傳送數(shù)據(jù)或接收輸入的數(shù)據(jù))压固。
當(dāng)應(yīng)用程序要?jiǎng)?chuàng)建一個(gè)套接字時(shí),操作系統(tǒng)就返回一個(gè)小整數(shù)作為描述符冰啃,應(yīng)用程序則使用這個(gè)描述符來引用該套接字需要I/O請(qǐng)求的應(yīng)用程序請(qǐng)求操作系統(tǒng)打開一個(gè)文件邓夕。操作系統(tǒng)就創(chuàng)建一個(gè)文件描述符提供給應(yīng)用程序訪問文件刘莹。從應(yīng)用程序的角度看阎毅,文件描述符是一個(gè)整數(shù),應(yīng)用程序可以用它來讀寫文件点弯。下圖顯示扇调,操作系統(tǒng)如何把文件描述符實(shí)現(xiàn)為一個(gè)指針數(shù)組,這些指針指向內(nèi)部數(shù)據(jù)結(jié)構(gòu)抢肛。
對(duì)于每個(gè)程序系統(tǒng)都有一張單獨(dú)的表狼钮。精確地講,系統(tǒng)為每個(gè)運(yùn)行的進(jìn)程維護(hù)一張單獨(dú)的文件描述符表捡絮。當(dāng)進(jìn)程打開一個(gè)文件時(shí)熬芜,系統(tǒng)把一個(gè)指向此文件內(nèi)部數(shù)據(jù)結(jié)構(gòu)的指針寫入文件描述符表,并把該表的索引值返回給調(diào)用者 福稳。應(yīng)用程序只需記住這個(gè)描述符涎拉,并在以后操作該文件時(shí)使用它。操作系統(tǒng)把該描述符作為索引訪問進(jìn)程描述符表,通過指針找到保存該文件所有的信息的數(shù)據(jù)結(jié)構(gòu)鼓拧。
文件描述符和文件指針的區(qū)別:
- 文件描述符:在linux系統(tǒng)中打開文件就會(huì)獲得文件描述符半火,它是個(gè)很小的正整數(shù)。每個(gè)進(jìn)程在PCB(Process Control Block)中保存著一份文件描述符表季俩,文件描述符就是這個(gè)表的索引钮糖,每個(gè)表項(xiàng)都有一個(gè)指向已打開文件的指針。
- 文件指針:C語言中使用文件指針做為I/O的句柄酌住。文件指針指向進(jìn)程用戶區(qū)中的一個(gè)被稱為FILE結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu)店归。FILE結(jié)構(gòu)包括一個(gè)緩沖區(qū)和一個(gè)文件描述符。而文件描述符是文件描述符表的一個(gè)索引酪我,因此從某種意義上說文件指針就是句柄的句柄(在Windows系統(tǒng)上娱节,文件描述符被稱作文件句柄)。
Socket工作流程
核心函數(shù)講解
通過以上的圖我們基本了解socket的
socket()
int socket(int domain, int type, int protocol); //返回sockfd(描述符)
對(duì)應(yīng)于普通文件的打開操作返回一個(gè)文件描述字祭示,而socket()用于創(chuàng)建一個(gè)socket描述符(socket descriptor)肄满,它唯一標(biāo)識(shí)一個(gè)socket。這個(gè)socket描述字跟文件描述字一樣质涛,后續(xù)的操作都有用到它稠歉,把它作為參數(shù),通過它來進(jìn)行一些讀寫操作汇陆。
- domain:即協(xié)議域怒炸,又稱為協(xié)議族(family)
協(xié)議族決定了socket的地址類型,在通信中必須采用對(duì)應(yīng)的地址毡代,如AF_INET決定了要用ipv4地址(32位的)與端口號(hào)(16位的)的組合阅羹、AF_UNIX決定了要用一個(gè)絕對(duì)路徑名作為地址。
family | 說明 |
---|---|
AF_INET | IPv4協(xié)議 |
AF_INET6 | IPv6 |
AF_LOCAL | Unix域協(xié)議 |
AF_ROUTE | 路由套接字 |
AF_KEY | 密鑰套接字 |
- type: 指定socket類型教寂。
type | 說明 |
---|---|
SOCK_STREAM(常用) | 字節(jié)流套接字 |
SOCK_DGRAM | 數(shù)據(jù)報(bào)套接字 |
SOCK_SEQPACKET | 有序分組套接字 |
SOCK_RAW | 原始套接字 |
- protocol:指定協(xié)議捏鱼。
protocol | 說明 |
---|---|
IPPROTO_TCP | TCP傳輸協(xié)議 |
IPPROTO_UDP | UDP傳輸協(xié)議 |
IPPROTO_SCTP | SCTP傳輸協(xié)議 |
IPPROTO_TIPC | TIPC傳輸協(xié)議 |
當(dāng)我們調(diào)用socket創(chuàng)建一個(gè)socket時(shí),返回的socket描述字它存在于協(xié)議族(address family酪耕,AF_XXX)空間中导梆,但沒有一個(gè)具體的地址。如果想要給它賦值一個(gè)地址迂烁,就必須調(diào)用bind()函數(shù)看尼,否則就當(dāng)調(diào)用connect()、listen()時(shí)系統(tǒng)會(huì)自動(dòng)隨機(jī)分配一個(gè)端口盟步。
注意:并不是上面的type和protocol可以隨意組合的藏斩,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當(dāng)protocol為0時(shí)却盘,會(huì)自動(dòng)選擇type類型對(duì)應(yīng)的默認(rèn)協(xié)議狰域。
bind
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
創(chuàng)建完socket之后就是地址的綁定了窜觉,通過bind系統(tǒng)調(diào)用實(shí)現(xiàn)。該調(diào)用通過傳遞進(jìn)來的文件描述符找到對(duì)應(yīng)的socket結(jié)構(gòu)北专,把一個(gè)地址族中的特定地址賦給socket禀挫,也可以說是綁定ip端口和socket。
- addr:一個(gè)const struct sockaddr *指針拓颓,指向要綁定給sockfd的協(xié)議地址语婴。這個(gè)地址結(jié)構(gòu)根據(jù)地址創(chuàng)建socket時(shí)的地址協(xié)議族的不同而不同。
- ipv4:sockaddr_in
- ipv6:sockaddr_in6
- Unix域:sockaddr_un
通常服務(wù)器在啟動(dòng)的時(shí)候都會(huì)綁定一個(gè)眾所周知的地址(如ip地址+端口號(hào))驶睦,用于提供服務(wù)砰左,客戶就可以通過它來接連服務(wù)器;而客戶端就不用指定场航,有系統(tǒng)自動(dòng)分配一個(gè)端口號(hào)和自身的ip地址組合缠导。這就是為什么通常服務(wù)器端在listen之前會(huì)調(diào)用bind(),而客戶端就不會(huì)調(diào)用溉痢,而是在connect()時(shí)由系統(tǒng)隨機(jī)生成一個(gè)僻造。
struct sockaddr和struct sockaddr_in
這兩個(gè)結(jié)構(gòu)體用來處理網(wǎng)絡(luò)通信的地址。在各種系統(tǒng)調(diào)用或者函數(shù)中孩饼,只要和網(wǎng)絡(luò)地址打交道髓削,就得用到這兩個(gè)結(jié)構(gòu)體。
include <netinet/in.h>
struct sockaddr {
unsigned short sa_family; // 2 bytes address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
// IPv4 AF_INET sockets:
struct sockaddr_in {
short sin_family; // 2 bytes e.g. AF_INET, AF_INET6
unsigned short sin_port; // 2 bytes e.g. htons(3490)
struct in_addr sin_addr; // 4 bytes see struct in_addr, below
char sin_zero[8]; // 8 bytes zero this if you want to
};
struct in_addr {
unsigned long s_addr; // 4 bytes load with inet_pton()
};
注釋中標(biāo)明了屬性的含義及其字節(jié)大小镀娶,這兩個(gè)結(jié)構(gòu)體一樣大立膛,都是16個(gè)字節(jié),而且都有family屬性梯码,不同的是:
- sockaddr用其余14個(gè)字節(jié)來表示sa_data宝泵,
- sockaddr_in把14個(gè)字節(jié)拆分成sin_port, sin_addr和sin_zero分別表示端口、ip地址轩娶。sin_zero用來填充字節(jié)使sockaddr_in和sockaddr保持一樣大小儿奶。
sckaddr和sockaddr_in包含的數(shù)據(jù)都是一樣的,但他們?cè)谑褂蒙嫌袇^(qū)別:
程序員不應(yīng)操作sockaddr罢坝,sockaddr是給操作系統(tǒng)用的廓握。程序員應(yīng)使用sockaddr_in來表示地址,sockaddr_in區(qū)分了地址和端口嘁酿,使用更方便。
listen
int listen(int sockfd, int backlog);
和listen相關(guān)的大部分信息存儲(chǔ)在inet_connection_sock結(jié)構(gòu)中男应。同樣的內(nèi)核通過文件描述符找到對(duì)應(yīng)的sock闹司,然后將其轉(zhuǎn)換為inet_connection_sock結(jié)構(gòu)。在inet_connection_sock結(jié)構(gòu)體中含有一個(gè)類型為request_sock_queue的icsk_accept_queue變量
inet_connection_sock.在linux內(nèi)核代碼中linux-2.6.32.12\include\net\inet_connection_sock.h
struct inet_connection_sock {
/* inet_sock has to be the first member! */
struct inet_sock icsk_inet;
struct request_sock_queue icsk_accept_queue;
struct inet_bind_bucket *icsk_bind_hash;
//....省略后面的代碼
}
- 第二個(gè)參數(shù)規(guī)定了內(nèi)核要為該套接字排隊(duì)的最大連接個(gè)數(shù)沐飘。
- 在創(chuàng)建套接字的時(shí)候使用了socket函數(shù)游桩,它創(chuàng)建的套接字是主動(dòng)套接字牲迫,listen函數(shù)的功能就是通過這個(gè)將主動(dòng)套接字,變成被動(dòng)套接字借卧,告訴內(nèi)核應(yīng)該接受指向這個(gè)套接字的請(qǐng)求,CLOSED狀態(tài)變成LISTEN狀態(tài)
accept
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
該調(diào)用創(chuàng)建新的struct socket表示新的連接
- 1盹憎、如果第二三個(gè)參數(shù)為空,代表了铐刘,我們對(duì)客戶的身份不感興趣陪每,因此置為NULL;
- 2镰吵、第一個(gè)參數(shù)為socket創(chuàng)建的監(jiān)聽套接字檩禾,返回的是已連接套接字,兩個(gè)套接字是有區(qū)別的疤祭,而且非常重要盼产。區(qū)別:我們所創(chuàng)建的監(jiān)聽套接字一般服務(wù)器只創(chuàng)建一個(gè),并且一直存在勺馆。而內(nèi)核會(huì)為每一個(gè)服務(wù)器進(jìn)程的客戶連接建立一個(gè)連接套接字戏售,當(dāng)服務(wù)器完成對(duì)某個(gè)給定客戶的服務(wù)時(shí),連接套接字就會(huì)被關(guān)閉草穆。
如果accept成功返回蜈项,則服務(wù)器與客戶已經(jīng)正確建立連接了,此時(shí)服務(wù)器通過accept返回的套接字來完成與客戶的通信续挟。
accept默認(rèn)會(huì)阻塞進(jìn)程紧卒,直到有一個(gè)客戶連接建立后返回,它返回的是一個(gè)新可用的套接字诗祸,這個(gè)套接字是連接套接字跑芳。
兩種套接字
- 監(jiān)聽套接字: 監(jiān)聽套接字正如accept的參數(shù)sockfd,它是監(jiān)聽套接字直颅,在調(diào)用listen函數(shù)之后博个,是服務(wù)器開始調(diào)用socket()函數(shù)生成的,稱為監(jiān)聽socket描述字(監(jiān)聽套接字)
- 連接套接字:一個(gè)套接字會(huì)從主動(dòng)連接的套接字變身為一個(gè)監(jiān)聽套接字功偿;而accept函數(shù)返回的是已連接socket描述字(一個(gè)連接套接字)盆佣,它代表著一個(gè)網(wǎng)絡(luò)已經(jīng)存在的點(diǎn)點(diǎn)連接。
一個(gè)服務(wù)器通常通常僅僅只創(chuàng)建一個(gè)監(jiān)聽socket描述字械荷,它在該服務(wù)器的生命周期內(nèi)一直存在共耍。內(nèi)核為每個(gè)由服務(wù)器進(jìn)程接受的客戶連接創(chuàng)建了一個(gè)已連接socket描述字,當(dāng)服務(wù)器完成了對(duì)某個(gè)客戶的服務(wù)吨瞎,相應(yīng)的已連接socket描述字就被關(guān)閉痹兜。
為什么要有兩種套接字?
原因很簡(jiǎn)單颤诀,如果使用一個(gè)描述字的話字旭,那么它的功能太多对湃,使得使用很不直觀,同時(shí)在內(nèi)核確實(shí)產(chǎn)生了一個(gè)這樣的新的描述字遗淳。
連接套接字socketfd_new 并沒有占用新的端口與客戶端通信拍柒,依然使用的是與監(jiān)聽套接字socketfd一樣的端口號(hào),此時(shí)我們需要區(qū)分兩種套接字
connect
connect函數(shù)的第一個(gè)參數(shù)即為客戶端的socket描述字屈暗,第二參數(shù)為服務(wù)器的socket地址拆讯,第三個(gè)參數(shù)為socket地址的長度】纸酰客戶端通過調(diào)用connect函數(shù)來建立與TCP服務(wù)器的連接往果。
close
在服務(wù)器與客戶端建立連接之后,會(huì)進(jìn)行一些讀寫操作一铅,完成了讀寫操作就要關(guān)閉相應(yīng)的socket描述字陕贮,好比操作完打開的文件要調(diào)用fclose關(guān)閉打開的文件。
#include <unistd.h>
int close(int fd);
close一個(gè)TCP socket的缺省行為時(shí)把該socket標(biāo)記為以關(guān)閉潘飘,然后立即返回到調(diào)用進(jìn)程肮之。該描述字不能再由調(diào)用進(jìn)程使用,也就是說不能再作為read或write的第一個(gè)參數(shù)卜录。
注意:close操作只是使相應(yīng)socket描述字的引用計(jì)數(shù)-1戈擒,只有當(dāng)引用計(jì)數(shù)為0的時(shí)候,才會(huì)觸發(fā)TCP客戶端向服務(wù)器發(fā)送終止連接請(qǐng)求艰毒。
accept獲取客戶端信息(ip和端口)
如果服務(wù)端不需要知道客戶端的信息筐高,accept的第二個(gè)和第三個(gè)參數(shù)可以傳NULL。如果需要?jiǎng)t傳入struct sockaddr*和socklen_t 丑瞧。而這里struct sockaddr指向struct sockaddr_in柑土。socklen_t *指向int。sockaddr_in的數(shù)據(jù)結(jié)構(gòu)上一節(jié)已經(jīng)講到绊汹。
struct sockaddr_in sockaddrClient;
int clientl=sizeof(sockaddrClient);
// if((connfd = accept(sock,NULL,NULL))==-1) {
if((connfd = accept(sock,(struct sockaddr*)&sockaddrClient,(socklen_t *)&clientl))==-1) {
printf("accpet socket error: %s errno :%d\n",strerror(errno),errno);
}
unsigned short port= ntohs(sockaddrClient.sin_port);
const char *ip=inet_ntop(AF_INET,(void *)&sockaddrClient.sin_addr,cilentIp,16);
printf("client %s:%d\n",cilentIp,port);
字符串ip和網(wǎng)絡(luò)二進(jìn)制的轉(zhuǎn)換
inet_ntop
把IPv4 and IPv6的地址從二進(jìn)制轉(zhuǎn)化成字符串稽屏。函數(shù)成功的話返回字符串的首地址,錯(cuò)誤返回NULL西乖;
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
- af 可以是AF_INET或AF_INET6
- src 一個(gè)指向網(wǎng)絡(luò)字節(jié)序的二進(jìn)制值的指針
- 轉(zhuǎn)換后的點(diǎn)分十進(jìn)制串的指針
- socklen_t size 代表緩沖區(qū)dst的大小狐榔,避免溢出,如果緩存區(qū)太小無法存儲(chǔ)地址的值获雕,則返回一個(gè)空指針薄腻,并將errno置為ENOSPC。
示例:
const char *ip=inet_ntop(AF_INET,(void *)&sockaddrClient.sin_addr,cilentIp,16);
if(ip!=NULL){
printf("ip is :%s",ip);
}
inet_pton
將ipv4和ipv6的點(diǎn)十進(jìn)制的ip字符串轉(zhuǎn)換成二進(jìn)制典鸡。
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
參數(shù)的含義與inet_ntop相似被廓,只是少了一個(gè)參數(shù)。
示例:
char *servInetAddr = "127.0.0.1";
//端口號(hào)由外部傳入
int port=atoi(argv[1]);
sockaddr.sin_port = htons(port);
inet_pton(AF_INET,servInetAddr,&sockaddr.sin_addr);
大小端問題
不同的CPU有不同的字節(jié)順序類型萝玷,這些字節(jié)順序類型指的是整數(shù)在內(nèi)存中保存的順序嫁乘,即主機(jī)字節(jié)順序。
Intelx86 的機(jī)器都是小端對(duì)齊模式球碉。
小端轉(zhuǎn)大端
- htons
整型變量從主機(jī)字節(jié)順序轉(zhuǎn)變成網(wǎng)絡(luò)字節(jié)順序蜓斧,也就是小端轉(zhuǎn)大端。 - hotnl
將主機(jī)數(shù)轉(zhuǎn)換成無符號(hào)長整型的網(wǎng)絡(luò)字節(jié)順序睁冬。
大端轉(zhuǎn)小端
- ntohs
將一個(gè)16位數(shù)由網(wǎng)絡(luò)字節(jié)順序 - ntohl
將一個(gè)無符號(hào)長整形數(shù)從網(wǎng)絡(luò)字節(jié)順序
示例源碼
server.cpp
#include "stdio.h"
//socket相關(guān)函數(shù)需要
#include <sys/types.h>
#include <sys/socket.h>
//close函數(shù)需要
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <errno.h>
#include <arpa/inet.h>
#include <thread>
#include <iostream>
#include "SocketThread.h"
using namespace std;
#define MAXLINE 1024
int main(int argc,char ** argv){
int connfd;
char buff[MAXLINE];
int n;
//創(chuàng)建一個(gè)socket
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock==-1) {
printf("create socket failed\n" );
}
//監(jiān)聽的端口
unsigned short bindPort=8888;
if(argc>=2) {
//如果外部自定義了端口挎春,則用外部自定義的端口
bindPort=atoi(argv[1]);
}
printf("create socket success! %d\n", sock);
struct sockaddr_in sockaddr;
memset(&sockaddr,0,sizeof(sockaddr));
sockaddr.sin_family=AF_INET;
sockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
sockaddr.sin_port=htons(bindPort);
//開始綁定
//這里要注意要使用:: 這樣就調(diào)用的全局的bind函數(shù),而不是std中的bind
::bind(sock,(struct sockaddr *)&sockaddr,sizeof(sockaddr));
//監(jiān)聽豆拨,最多支持10個(gè)連接
listen(sock,10);
char cilentIp[20];
while (true) {
/* code */
struct sockaddr_in sockaddrClient;
int clientl=sizeof(sockaddrClient);
printf("wait for client connect\n" );
//不關(guān)心客戶端的信息
// if((connfd = accept(sock,NULL,NULL))==-1) {
//接受客戶端連接的同時(shí)直奋,獲取客戶端的信息
if((connfd = accept(sock,(struct sockaddr*)&sockaddrClient,(socklen_t *)&clientl))==-1) {
printf("accpet socket error: %s errno :%d\n",strerror(errno),errno);
}
//獲取端口
unsigned short port= ntohs(sockaddrClient.sin_port);
//網(wǎng)絡(luò)Ip=>主機(jī)Ip
const char *ip=inet_ntop(AF_INET,(void *)&sockaddrClient.sin_addr,cilentIp,16);
printf("client %s:%d\n",cilentIp,port);
//使用線程
SocketThread* st=new SocketThread(connfd);
thread t(&SocketThread::run,st);
t.detach();
}
int ret=close(sock);
if(ret==-1) {
printf("socket close failed\n");
}else{
printf("%d close success!\n",sock );
}
return 0;
}
client.cpp
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MAXLINE 1024
int main(int argc,char **argv)
{
//連接的地址
const char *servInetAddr = "127.0.0.1";
int socketfd;
struct sockaddr_in sockaddr;
char recvline[MAXLINE], sendline[MAXLINE];
int n;
unsigned short port=8888;
if(argc >= 2)
{
//端口號(hào)由外部傳入
port=atoi(argv[1]);
}
socketfd = socket(AF_INET,SOCK_STREAM,0);
memset(&sockaddr,0,sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(port);
//主機(jī)Ip=>網(wǎng)絡(luò)Ip
inet_pton(AF_INET,servInetAddr,&sockaddr.sin_addr);
//連接服務(wù)端
if((connect(socketfd,(struct sockaddr*)&sockaddr,sizeof(sockaddr))) < 0 )
{
printf("connect error :[%s] errno: %d\n",strerror(errno),errno);
exit(0);
}
printf("send message to server\n");
int cmp=0;
while(true) {
printf("input your mssage\n");
fgets(sendline,1024,stdin);
//判斷輸入,如果是exit則退出并關(guān)閉連接
int cmp=strcmp(sendline,"exit\n");
if(cmp==0) {
break;
}
//發(fā)送數(shù)據(jù)施禾。
if((send(socketfd,sendline,strlen(sendline),0)) < 0)
{
printf("send mes error: [%s] errno : %d",strerror(errno),errno);
exit(0);
}
memset(sendline,0,sizeof(sendline));
}
close(socketfd);
printf("exit\n");
exit(0);
}
SocketThread.h
#ifndef SOCKET_THREAD_H
#define SOCKET_THREAD_H
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
using namespace std;
class SocketThread{
private:
int sock;
public:
SocketThread(int sock){
this->sock=sock;
}
~SocketThread(){
cout<<"release"<<endl;
}
void run(){
int n=0;
cout<<"new thread for socket "<<this->sock<<endl;
char buff[1024];
for(;; ) {
n = recv(this->sock,buff,1024,0);
if(n<=0) {
//如果客戶端斷開了脚线,這里就跳出循環(huán)
break;
}
buff[n] = '\0';
printf("%d=>%s",n,buff);
}
close(this->sock);
cout<<this->sock<<" closed"<<endl;
}
};
#endif
Makefile文件
all: server client
objects = SocketThread.h server.cpp
server : $(objects)
# g++ -o server $(objects)
g++ -lpthread -o server $(objects) -std=c++0x
client : client.cpp
g++ -o client client.cpp
.PHONY : clean
clean :
-rm server client