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++ 編譯的平臺。