linux下Socket編程(一)

簡(jiǎn)介

  • Socket理論
  • Socket工作流程
  • 核心函數(shù)講解
  • 服務(wù)的如何獲取客戶端的信息
  • 字符串ip和網(wǎng)絡(luò)二進(jìn)制的轉(zhuǎn)換
  • 大小端問題
  • 示例源代碼

Linux內(nèi)核源碼

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工作流程

Socket.png

核心函數(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 密鑰套接字
domain.png
  • 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é)順序

示例源碼

socket_project.png
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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市弥搞,隨后出現(xiàn)的幾起案子邮绿,更是在濱河造成了極大的恐慌,老刑警劉巖攀例,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件船逮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡粤铭,警方通過查閱死者的電腦和手機(jī)挖胃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梆惯,“玉大人酱鸭,你說我怎么就攤上這事〖哟” “怎么了凛辣?”我有些...
    開封第一講書人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長职烧。 經(jīng)常有香客問我扁誓,道長,這世上最難降的妖魔是什么蚀之? 我笑而不...
    開封第一講書人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任蝗敢,我火速辦了婚禮,結(jié)果婚禮上足删,老公的妹妹穿的比我還像新娘寿谴。我一直安慰自己,他們只是感情好失受,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開白布讶泰。 她就那樣靜靜地躺著咏瑟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痪署。 梳的紋絲不亂的頭發(fā)上码泞,一...
    開封第一講書人閱讀 49,842評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音狼犯,去河邊找鬼余寥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛悯森,可吹牛的內(nèi)容都是我干的宋舷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼瓢姻,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼祝蝠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汹来,我...
    開封第一講書人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤续膳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后收班,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坟岔,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年摔桦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了社付。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邻耕,死狀恐怖鸥咖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兄世,我是刑警寧澤啼辣,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站御滩,受9級(jí)特大地震影響鸥拧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望应闯。 院中可真熱鬧,春花似錦拨匆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盏缤。三九已至砰蠢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蛾找,已是汗流浹背娩脾。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來泰國打工赵誓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留打毛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓俩功,卻偏偏與公主長得像幻枉,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诡蜓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • Linux下的Socket編程(主要包括TCP部分) 轉(zhuǎn)載麻煩注明原文地址本文是Linux下基本的Socket編程...
    Bugfix閱讀 2,828評(píng)論 0 8
  • 1三個(gè)相關(guān)數(shù)據(jù)結(jié)構(gòu). 關(guān)于socket的創(chuàng)建熬甫,首先需要分析socket這個(gè)結(jié)構(gòu)體,這是整個(gè)的核心蔓罚。 104 str...
    ice_camel閱讀 2,808評(píng)論 1 8
  • 一椿肩、基本socket函數(shù)Linux系統(tǒng)是通過提供套接字(socket)來進(jìn)行網(wǎng)絡(luò)編程的。網(wǎng)絡(luò)的socket數(shù)據(jù)傳輸...
    WB莫遙燚閱讀 1,470評(píng)論 0 0
  • 網(wǎng)絡(luò)模型 物理層 物理層表示的是比特流傳輸豺谈,通常包括串口/COM口郑象、并行/LPT口、USB茬末、網(wǎng)線接口厂榛、電話線接口;...
    秋風(fēng)弄影閱讀 710評(píng)論 0 2
  • 觀察:早上9點(diǎn)有老家鄰居霞霞發(fā)信息向我借錢丽惭,我的第一反應(yīng)就是驚呀击奶,我們平時(shí)聯(lián)系不多,不能亂借责掏,中午我電話聯(lián)系柜砾、了解...
    陳誠chen閱讀 196評(píng)論 0 0