作者:龍飛
話題回到“黑社會辦公室”的例子,講概念已經(jīng)扯得比較遠(yuǎn)了,不過,這一節(jié)我們還得講概念,不過好在有些程序的例子。如果大家不想翻回去看TcpServer類的原型,我這里直接給出這個頭文件的完整源代碼:
//Filename: TcpServerClass.hpp
#ifndef TCPSERVERCLASS_HPP_INCLUDED
#define TCPSERVERCLASS_HPP_INCLUDED
#include <unistd.h>
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
class TcpServer
{
private:
int listenSock;
int communicationSock;
sockaddr_in servAddr;
sockaddr_in clntAddr;
public:
TcpServer(int listen_port);
bool isAccept();
void handleEcho();
};
#endif // TCPSERVERCLASS_HPP_INCLUDED
我們已經(jīng)解釋了為什么listenSock和communicationSock的類型是int,以及sockaddr_in是什么結(jié)構(gòu),現(xiàn)在來寫這個類的構(gòu)造函數(shù):
TcpServer::TcpServer(int listen_port)
{
if ( (listenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 ) {
throw "socket() failed";
}
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(listen_port);
if ( bind(listenSock, (sockaddr*)&servAddr, sizeof(servAddr)) < 0 ) {
throw "bind() failed";
}
if ( listen(listenSock, 10) < 0 ) {
throw "listen() failed";
}
}
好,先看看程序培養(yǎng)一下感覺,我們還得說概念。
數(shù)據(jù)封裝(Data Encapsutation)
我們前面說到了網(wǎng)絡(luò)分層:鏈路——網(wǎng)絡(luò)——傳輸——應(yīng)用。數(shù)據(jù)從應(yīng)用程序里誕生,傳送到互聯(lián)網(wǎng)上每一層都會進(jìn)行一次封裝:
Data>>Application>>TCP/UDP>>IP>>OS(Driver, Kernel & Physical Address)
我們用socket重點(diǎn)描述的是協(xié)議,包括網(wǎng)絡(luò)協(xié)議(IP)和傳輸協(xié)議(TCP/UDP)。
sockaddr重點(diǎn)描述的是地址,包括IP地址和TCP/UDP端口。
socket()函數(shù)
我們從TcpServer::TcpServer()函數(shù)可以看到,socket和sockaddr的產(chǎn)生是可以相互獨(dú)立的。socket()的函數(shù)原型是:
int socket(int protocolFamily, int type, int protocol);
在Linux中的實(shí)現(xiàn)為:
#include <sys/socket.h>
/* Create a new socket of type TYPE in domain DOMAIN, using
protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
Returns a file descriptor for the new socket, or -1 for errors. */
extern int socket (int __domain, int __type, int __protocol) __THROW;
第一個參數(shù)是協(xié)議簇(Linux里面叫作域,意思一樣的),還是那句話,我們這篇教程用到的就僅僅是一個PF_INET(protocol family : internet),很多時候你會發(fā)現(xiàn)人們也經(jīng)常在這里賦值為AF_INET,事實(shí)上,當(dāng)前,AF_INET就是PF_INET的一個#define,但是,寫成PF_INET從語義上會更加嚴(yán)謹(jǐn)。這也就是TCP/IP協(xié)議簇中的IP協(xié)議(Internet Protocol),網(wǎng)絡(luò)層的協(xié)議。
后面兩個參數(shù)定義傳輸層的協(xié)議。
第二個參數(shù)是傳輸層協(xié)議類型,我們教程里用到的宏,只有兩個:SOCK_STREAM(數(shù)據(jù)流格式)和SOCK_DGRAM(數(shù)據(jù)報格式);(具體是什么我們以后討論)
第三個參數(shù)是具體的傳輸層協(xié)議。當(dāng)賦值為0的時候,系統(tǒng)會根據(jù)傳輸層協(xié)議類型自動匹配和選擇。事實(shí)上,當(dāng)前,匹配SOCK_STREAM的就是TCP協(xié)議;而匹配SOCK_DGRAM就是UDP協(xié)議。所以,我們指定了第二個參數(shù),第三個就可以簡單的設(shè)置為0。不過,為了嚴(yán)謹(jǐn),我們最好還是把具體協(xié)議寫出來,比如,我們的例子中的TCP協(xié)議的宏名稱:IPPROTO_TCP。
數(shù)據(jù)的“地址”
從數(shù)據(jù)封裝的模型,我們可以看到數(shù)據(jù)是怎么從應(yīng)用程序傳遞到互聯(lián)網(wǎng)的。我們說過,數(shù)據(jù)的傳送是通過socket進(jìn)行的。但是socket只描述了協(xié)議類型。要讓數(shù)據(jù)正確的傳送到某個地方,必須添加那個地方的sockaddr地址;同樣,要能接受網(wǎng)絡(luò)上的數(shù)據(jù),必須有自己的sockaddr地址。
可見,在網(wǎng)絡(luò)上傳送的數(shù)據(jù)包,是socket和sockaddr共同“染指”的結(jié)果。他們共同封裝和指定了一個數(shù)據(jù)包的網(wǎng)絡(luò)協(xié)議(IP)和IP地址,傳輸協(xié)議(TCP/UDP)和端口號。
網(wǎng)絡(luò)字節(jié)和本機(jī)字節(jié)的相互轉(zhuǎn)換
sockaddr結(jié)構(gòu)中的IP地址(sin_addr.s_addr)和端口號(sin_port)將被封裝到網(wǎng)絡(luò)上傳送的數(shù)據(jù)包中,所以,它的結(jié)構(gòu)形式需要保證是網(wǎng)絡(luò)字節(jié)形式。我們這里用到的函數(shù)是htons()和htonl(),這些縮寫的意思是:
h: host,主機(jī)(本機(jī))
n: network,網(wǎng)絡(luò)
to: to轉(zhuǎn)換
s: short,16位(2字節(jié),常用于端口號)
l: long, 32位(4字節(jié),常用于IP地址)
“反過來”的函數(shù)也是存在的ntohs()和ntohl()。
動作與持續(xù)行為
本節(jié)最后的一個概念可以跟計(jì)算機(jī)無關(guān)。作為動詞,有些可以描述動作,有些是描述一重持續(xù)的行為狀態(tài)的(就如同一般動詞和be動詞一樣)。扯到C++來說,我們可以把持續(xù)行為封裝到函數(shù)內(nèi)部,只留出動作的接口。事實(shí)上,構(gòu)造函數(shù)中的bind()和listen()就是這種描述持續(xù)狀態(tài)的行為函數(shù)。
posted on 2008-07-12 13:27
lf426 閱讀(5148)
評論(0) 編輯 收藏 引用 所屬分類:
SDL入門教程 、
Linux與C++ 、
socket 編程入門教程