1. 程式人生 > >C++伺服器(一):瞭解Linux下socket程式設計

C++伺服器(一):瞭解Linux下socket程式設計

最近想要用C++寫個socket的伺服器,用於日常的專案開發。
不過,我是新手,那就慢慢地學習一下吧。

Server

#include<iostream>
using namespace std;

//head files of Linux
#include<netinet/in.h>
#include<unistd.h>   //for fork and read
#include<sys/types.h>   //for socket
#include<sys/socket.h>  //for socket
#include<string.h> // for bzero
#include<arpa/inet.h> void server() { const unsigned short SERVERPORT = 53556; const int BACKLOG = 10; //10 個最大的連線數 const int MAXSIZE = 1024; int sock, client_fd; sockaddr_in myAddr; sockaddr_in remoteAddr; sock = socket(AF_INET, SOCK_STREAM, 0); //create socket if
( sock == -1) { cerr<<"socket create fail!"<<endl; exit(1); } cout<<"sock :"<<sock<<endl; //bind myAddr.sin_family = AF_INET; myAddr.sin_port = htons(SERVERPORT); myAddr.sin_addr.s_addr = INADDR_ANY; bzero( &(myAddr.sin_zero), 8
); if(bind(sock, (sockaddr*)(&myAddr), sizeof(sockaddr)) ==-1 ) { cerr<<"bind error!"<<endl; exit(1); } //listen if(listen(sock, BACKLOG) == -1) { cerr<<"listen error"<<endl; exit(1); } while(true) { unsigned int sin_size = sizeof(sockaddr_in); if( (client_fd = accept(sock, (sockaddr*)(&remoteAddr), &sin_size)) ==-1 ) { cerr<<"accept error!"<<endl; continue; } cout<<"Received a connection from "<<static_cast<char*>(inet_ntoa(remoteAddr.sin_addr) )<<endl; //子執行緒 if(!fork() ) { int rval; char buf[MAXSIZE]; if( (rval = read(client_fd, buf, MAXSIZE) ) <0) { cout<<"Reading stream error!\n"; continue; } cout<<buf<<endl; //向客戶端傳送資訊 const char* msg = "Hello, I am xiaojian. You are connected !"; if( send(client_fd, const_cast<char*>(msg), strlen(msg), 0) == -1) cerr<<"send error!"<<endl; close(client_fd); exit(0); } } } int main() { server(); }

Client

#include<iostream>
using namespace std;

#include<string.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
void client()
{
    const unsigned short SERVERPORT = 53556;
    const int MAXSIZE = 1024;
    const char* SERVER_IP = "115.159.90.99";
    const char* DATA = "this is a client message ";

    int sock, recvBytes;
    char buf[MAXSIZE];
//    hostent *host;
    sockaddr_in serv_addr;

    if( (sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        cerr<<"socket create fail!"<<endl;
        exit(1);
    }
    bzero( &serv_addr, sizeof(serv_addr) );
    serv_addr.sin_family =  AF_INET;
    serv_addr.sin_port = htons(SERVERPORT);
    serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    if( connect(sock, (sockaddr*)&serv_addr, sizeof(sockaddr)) == -1)
    {
        cerr<<"connect error"<<endl;
        exit(1);
    }

    write(sock, const_cast<char*>(DATA), strlen(DATA) );
    if( (recvBytes = recv(sock, buf, MAXSIZE, 0)) == -1)
    {
        cerr<<"recv error!"<<endl;
        exit(1);
    }

    buf[recvBytes] = '\0';
    cout<<buf<<endl;
    close(sock);
}

int main()
{
    client();
}

程式碼比較容易理解,主要是各種 API 的理解和使用。

一些函式和結構

sockaddr_in

解釋一下程式碼:
首先看到一個結構體:sockaddr_in,這是什麼結構呢
sockaddr_in 在標頭檔案in.h中宣告,這個標頭檔案在/usr/include/netinet/目錄下,去一看究竟,可以找到它的宣告:

/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;         /* Port number.  */
    struct in_addr sin_addr;        /* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
               __SOCKADDR_COMMON_SIZE -
               sizeof (in_port_t) -
               sizeof (struct in_addr)];
  };
    myAddr.sin_family = AF_INET;
    myAddr.sin_port = htons(SERVERPORT);
    myAddr.sin_addr.s_addr = INADDR_ANY;
    bzero( &(myAddr.sin_zero), 8);

其中 AF_INET 定義了協議族(TCP\UDP等)。

socket函式

函式名: socket(建立一個socket通訊)
表頭檔案: #include<sys/types.h> #include<sys/socket.h>
定義函式: int socket(int domain,int type,int protocol);
函式說明: socket()用來建立一個新的socket,也就是向系統註冊,通知系統建立一通訊埠。引數domain 指定使用何種的地址型別,完整的定義在/usr/include/bits/socket.h內。
返回值: 成功則返回socket處理程式碼,失敗返回-1。

bind函式

函式名: bind(對socket定位)
表頭檔案: #include<sys/types.h> #include<sys/socket.h>
定義函式: int bind(int sockfd,struct sockaddr * my_addr,int addrlen);
函式說明: bind()用來設定給引數sockfd的socket一個名稱。此名稱由引數my_addr指向一sockaddr結構,對於不同的socket domain定義了一個通用的資料結構
返回值: 成功則返回0,失敗返回-1,錯誤原因存於errno中。

listen函式

函式名: listen(等待連線)
表頭檔案: #include<sys/socket.h>
定義函式: int listen(int s,int backlog);
函式說明: listen()用來等待引數s 的socket連線。引數backlog指定同時能處理的最大連線要求,如果連線數目達此上限則client端將收到ECONNREFUSED的錯誤。Listen()並未開始接收連線,只是設定socket為listen模式,真正接收client端連線的是accept()。通常listen()會在socket(),bind()之後呼叫,接著才呼叫accept()。
返回值: 成功則返回0,失敗返回-1,錯誤原因存於errno。
附加說明: listen()只適用SOCK_STREAM或SOCK_SEQPACKET的socket型別。如果socket為AF_INET則引數backlog 最大值可設至128。

accept函式

函式名: accept(接受socket連線)
表頭檔案: #include<sys/types.h> #include<sys/socket.h>
定義函式: int accept(int s,struct sockaddr * addr,int * addrlen);
函式說明: accept()用來接受引數s的socket連線。引數s的socket必需先經bind()、listen()函式處理過,當有連線進來時accept()會返回一個新的socket處理程式碼,往後的資料傳送與讀取就是經由新的socket處理,而原來引數s的socket能繼續使用accept()來接受新的連線要求。連線成功時,引數addr所指的結構會被系統填入遠端主機的地址資料,引數addrlen為scokaddr的結構長度。關於結構sockaddr的定義請參考bind()。
返回值: 成功則返回新的socket處理程式碼,失敗返回-1,錯誤原因存於errno中。

send函式

函式名: send(經socket傳送資料)
表頭檔案: #include<sys/types.h> #include<sys/socket.h>
定義函式: int send(int s,const void * msg,int len,unsigned int falgs);
函式說明: send()用來將資料由指定的socket 傳給對方主機。引數s為已建立好連線的socket。引數msg指向欲連線的資料內容,引數len則為資料長度。引數flags一般設0。
返回值: 成功則返回實際傳送出去的字元數,失敗返回-1。錯誤原因存於errno

recv函式

函式名: recv(經socket接收資料)
表頭檔案: #include<sys/types.h> #include<sys/socket.h>
定義函式: int recv(int s,void *buf,int len,unsigned int flags);
函式說明: recv()用來接收遠端主機經指定的socket傳來的資料,並把資料存到由引數buf 指向的記憶體空間,引數len為可接收資料的最大長度。
返回值: 接收的實際長度

connect函式

函式名: connect(建立socket連線)
表頭檔案: #include<sys/types.h> #include<sys/socket.h>
定義函式: int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
函式說明: connect()用來將引數sockfd 的socket 連至引數serv_addr 指定的網路地址。結構sockaddr請參考bind()。引數addrlen為sockaddr的結構長度。
返回值: 成功則返回0,失敗返回-1,錯誤原因存於errno中。

其他函式

read : 由已開啟的檔案讀取資料
write: 將資料寫入已開啟的檔案內
htons:將16位主機字元順序轉換成網路字元順序
bzero:將一段記憶體內容全清為零
inet_addr:將網路地址轉成二進位制的數字

函式可以在參考資料中的Linux 常用手冊找到。
但為了程式碼的可相容性,我個人的意見是,儘量少用依賴於平臺的函式,多用標準庫,這樣程式碼可以輕易移植到其他支援 C++ 編譯的平臺。