你需要了解的一些系統調用:
#include <sys/socket.h>
int socket(int domain , int type , int protocol);
#include <sys/socket.h>
int bind (int sockfd , struct sockaddr *my_addr , int addrlen) ;
參數說明:
l sockfd 是由socket()函數返回的套接字描述符。
l my_addr 是一個指向struct sockaddr 的指針,包含有關你的地址的信息:名稱、端口和IP 地址。
l addrlen 可以設置爲sizeof(struct sockaddr)。
相關函數
|
socket,accept,connect,listen |
表頭文件
|
#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定義了一個通用的數據結構 struct sockaddr { unsigned short int sa_family; char sa_data[14]; }; sa_family 爲調用socket()時的domain參數,即AF_xxxx值。 sa_data 最多使用14個字符長度。 此sockaddr結構會因使用不同的socket domain而有不同結構定義,例如使用AF_INET domain,其socketaddr結構定義便爲 struct socketaddr_in { unsigned short int sin_family; uint16_t sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; }; struct in_addr { uint32_t s_addr; }; sin_family 即爲sa_family sin_port 爲使用的port編號 sin_addr.s_addr 爲IP 地址 sin_zero 未使用。 |
參數
|
addrlen爲sockaddr的結構長度。 |
返回值
|
成功則返回0,失敗返回-1,錯誤原因存於errno中。 |
錯誤代碼
|
EBADF 參數sockfd 非合法socket處理代碼。 EACCESS 權限不足 ENOTSOCK 參數sockfd爲一文件描述詞,非socket。 |
表頭文件
|
#include<netinet/in.h> |
定義函數
|
unsigned long int htonl(unsigned long int hostlong); |
函數說明
|
htonl()用來將參數指定的32位hostlong 轉換成網絡字符順序。 |
返回值
|
返回對應的網絡字符順序。 |
#include <sys/socket.h>
int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);
connect()的三個參數意義如下:
l sockfd :套接字文件描述符,由socket()函數返回的。
l serv_addr 是一個存儲遠程計算機的IP 地址和端口信息的結構。
l addrlen 應該是sizeof(struct sockaddr)。
int listen(int sockfd, int backlog);
listen()函數的參數意義如下:
l sockfd 是一個套接字描述符,由socket()系統調用獲得。
l backlog 是未經過處理的連接請求隊列可以容納的最大數目。
backlog 具體一些是什麼意思呢?每一個連入請求都要進入一個連入請求隊列,等待listen 的程序調用accept()(accept()函數下面有介紹)函數來接受這個連接。當系統還沒有調用accept()函數的時候,如果有很多連接,那麼本地能夠等待的最大數目就是backlog 的數值。你可以將其設成5 到10 之間的數值(推薦)。像上面的所有函數一樣, listen()如果返回 –1 ,那麼說明在listen()的執行過程中發生了錯誤。全局變量errno 中存儲了錯誤代碼。
l 有人從很遠很遠的地方嘗試調用connect()來連接你的機器上的某個端口(當然是你已經在listen()的)。
l 他的連接將被listen 加入等待隊列等待accept()函數的調用(加入等待隊列的最多數目由調用listen()函數的第二個參數backlog 來決定)。
l 你調用accept()函數,告訴他你準備連接。
l accept()函數將回返回一個新的套接字描述符,這個描述符就代表了這個連接!
int accept(int sockfd, void *addr, int *addrlen);
accept()函數的參數意義如下:
l sockfd 是正在listen() 的一個套接字描述符。
l addr 一般是一個指向struct sockaddr_in 結構的指針;裏面存儲着遠程連接過來的計算機的信息(比如遠程計算機的IP 地址和端口)。
l addrlen 是一個本地的整型數值,在它的地址傳給accept() 前它的值應該是sizeof(struct sockaddr_in);accept()不會在addr 中存儲多餘addrlen bytes 大小的數據。如果
accept()函數在addr 中存儲的數據量不足addrlen,則accept()函數會改變addrlen 的值來反應這個情況。
#include <sys/socket.h>
int send(int sockfd, const void *msg, int len, int flags);
send 的參數含義如下:
l sockfd 是代表你與遠程程序連接的套接字描述符。
l msg 是一個指針,指向你想發送的信息的地址。
l len 是你想發送信息的長度。
l flags 發送標記。一般都設爲0(你可以查看send 的man pages 來獲得其他的參數
值並且明白各個參數所代表的含義)。
注意:send() 所發送的數據可能少於你給它的參數所指定的長度!因爲如果你給send()的參數中包含的數據的長度遠遠大於send()所能一次發送的數據,則send()函數
只發送它所能發送的最大數據長度,然後它相信你會把剩下的數據再次調用它來進行第二次發送。所以,記住如果send()函數的返回值小於len 的話,則你需要再次發送剩下的數據。幸運的是,如果包足夠小(小於1K),那麼send()一般都會一次發送光的。像上面的函數一樣,send()函數如果發生錯誤,則返回 –1 ,錯誤代碼存儲在全局變量errno 中。
#include <sys/types.h>
#include <sys/socket.h>
recv()的參數含義如下:
l sockfd 是你要讀取數據的套接字描述符。
l buf 是一個指針,指向你能存儲數據的內存緩存區域。
l len 是緩存區的最大尺寸。
l flags 是recv() 函數的一個標誌,一般都爲0 (具體的其他數值和含義請參考recv()
的man pages)。
recv() 返回它所真正收到的數據的長度。(也就是存到buf 中數據的長度)。如果返回–1 則代表發生了錯誤(比如網絡以外中斷、對方關閉了套接字連接等),全局變量errno 裏面存儲了錯誤代碼。
使用方法:
close(sockfd);
執行close()之後,套接字將不會在允許進行讀操作和寫操作。任何有關對套接字描述符進行讀和寫的操作都會接收到一個錯誤。
如果你想對網絡套接字的關閉進行進一步的操作的話,你可以使用函數shutdown()。
它允許你進行單向的關閉操作,或是全部禁止掉。
shutdown()的聲明爲:
#include <sys/socket.h>
int shutdown(int sockfd, int how);
它的參數含義如下:
l sockfd 是一個你所想關閉的套接字描述符.
l how 可以取下面的值。0 表示不允許以後數據的接收操;1 表示不允許以後數據的發送操作;2 表示和close()一樣,不允許以後的任何操作(包括接收,發送數據)
shutdown() 如果執行成功將返回0,如果在調用過程中發生了錯誤,它將返回–1,全局變量errno 中存儲了錯誤代碼.
如果你在一個未連接的數據報套接字上使用shutdown() 函數(還記得可以對數據報套接字UDP 進行connect()操作嗎?),它將什麼也不做.
新啓用同一個端口號,即使在你正常關閉該套接字以後。問題就是Linux 內核在一個綁定套接字的進程結束後從不把端口標記爲未用。
setsockopt()系統調用在其上設定選項(options)。setsockopt() 調用設置選項而getsockopt()
從給定的套接字取得選項。
這裏是這些調用的語法:
#include<sys/types.h>
#include<sys/socket.h>
int getsockopt(int sockfd, int level, int name, char *value, int *optlen);
int setsockopt(int sockfd, int level, int name, char *value, int *optlen);
下面是兩個調用的參數說明:
l sockfd 必須是一個已打開的套接字。
l level 是函數所使用的協議標準(protocol level)(TCP/IP 協議使用IPPROTO_TCP,
套接字標準的選項實用SOL_SOCKET)。
l name 選項在套接字說明書中(man page)有詳細說明。
l value 指向爲getsockopt()函數所獲取的值,setsockopt()函數所設置的值的地址。
l optlen 指針指向一個整數,該整數包含參數以字節計算的長度。
它的聲明爲:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
下面是參數說明:
l sockfd 是你想取得遠程信息的那個套接字描述符。
l addr 是一個指向struct sockaddr (或是struct sockaddr_in)的指針。
l addrlen 是一個指向int 的指針,應該賦於sizeof(struct sockaddr)的大小。
如果在函數執行過程中出現了錯誤,函數將返回 –1 ,並且錯誤代碼儲存在全局變量
errno 中。
當你擁有了遠程連接用戶的IP 地址,你就可以使用inet_ntoa() 或gethostbyaddr()來輸
出信息或是做進一步的處理。
它返回正在執行它的計算機的名字。返回的這個名字可以被gethostbyname()函數使用,
由此可以得到本地主機的IP 地址。
下面是它的聲明:
#include <unistd.h>
int gethostname(char *hostname, size_t size);
參數說明如下:
l hostname 是一個指向字符數組的指針,當函數返回的時候,它裏面的數據就是本
地的主機的名字.
l size 是hostname 指向的數組的長度.
函數如果成功執行,它返回0,如果出現錯誤,則返回–1,全局變量errno 中存儲着錯
誤代碼。
struct hostent *gethostbyname(const char *name);
正如你所看見的,它返回了一個指向struct hostent 的指針.Struct hostent 是這樣定義
的:
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
#define h_addr h_addr_list[0]
下面是上面各個域代表含義的解釋:
l h_name 是這個主機的正式名稱。
l h_aliases 是一個以NULL(空字符)結尾的數組,裏面存儲了主機的備用名稱。
l h_addrtype 是返回地址的類型,一般來說是“AF_INET”。
l h_length 是地址的字節長度。
l h_addr_list 是一個以0 結尾的數組,存儲了主機的網絡地址。
注意:網絡地址是以網絡字節順序存儲的。
l h_addr - h_addr_list 數組的第一個成員.
gethostbyname() 返回的指針指向結構struct hostent ,如果發生錯誤,它將會返回NULL
(但是errno 並不代表錯誤代碼,h_errno 中存儲的才識錯誤代碼。參考下面的herror()函數)。
h_errno 中而不是errno 中。所以,你需要調用herror()函數。
l 調用socket()函數創建一個套接字。
l 調用bind()函數把自己綁定在一個地址上。
l 調用listen()函數偵聽連接。
l 調用accept()函數接受所有引入的請求。
l 調用recv()函數獲取引入的信息然後調用send()回答。
【 getsockname系統調用】
功能描述:
返回指定套接字的名稱。
用法:
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *name, socklen_t *namelen);
參數:
sockfd:需要獲取名稱的套接字。
name:存放所獲取套接字名稱的緩衝區。
nemalen:作爲入口參數,name指向空間的最大長度。作爲出口參數,name的實際長度。
返回說明:
成功執行時,返回0。失敗返回-1,errno被設爲以下的某個值
EBADF:sock不是有效的文件描述詞
EFAULT:name指向的內存並非有效的進程空間
EINVAL:namelen無效,可能爲負值
ENOBUFS:執行操作時,系統資源不足
ENOTCONN:套接字尚未連接上
ENOTSOCK:sock描述的不是套接字
功能:
getsockname: 返回本地協議地址
getpeername:返回遠程協議地址
定義:
#include <sys/unistd.h>
int getsockname (int sockfd, struct sockaddr *localaddr, int *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, int *addrlen);
getsockname()函數用於獲取一個套接口的名字。它用於一個已捆綁或已連接套接口s,本地地址將被返回。本調用特別適用於如下情況:未調用bind()就調用了connect(),這時唯有getsockname()調用可以獲知系統內定的本地地址。在返回時,namelen參數包含了名字的實際字節數。
若一個套接口與INADDR_ANY捆綁,也就是說該套接口可以用任意主機的地址,此時除非調用connect()或accept()來連接,否則getsockname()將不會返回主機IP地址的任何信息。除非套接口被連接,WINDOWS套接口應用程序不應假設IP地址會從INADDR_ANY變成其他地址。這是因爲對於多個主機環境下,除非套接口被連接,否則該套接口所用的IP地址是不可知的。
Open C 套接字: getsockname 方法
getsockname - 獲取套接字名稱
int getsockname (int s, struct sockaddr * restrict name, socklen_t * restrict namelen);
getsockname系統調用返回指定套接字的當前名稱。namelen應被初始化指出name所指向的空間容量。返回時,該參數含有返回名稱
的實際大小(按字節)。
下面是getsockname函數的用法:
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
TInt GetSockName()
{
int sock_fd;
struct sockaddr_in addr,ss;
unsigned int len;
sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(5000);
bind(sock_fd,(struct sockaddr*)&addr,sizeof(addr));
len=sizeof(ss);
getsockname(sock_fd,(struct sockaddr*)&ss,&len);
close(sock_fd);
}
***********************************************************************************
LINUX下Socket編程筆記:http://blog.chinaunix.net/u/19185/article_56798.html
***********************************************************************************
程序實例:
###################################################################################
//使用方法:定義的服務器的遠端端口號是4000,故編譯運行該程序後,需要使用以下命令在終端上顯示:Hello World!
//在cmd命令行中輸入:telnet 遠端服務器地址 端口號
//我的實際運行 telnet 192.168.12.94 4000?
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
// 服務器要監聽的本地端口
#define MYPORT 4000
// 能夠同時接受多少沒有accept 的連接
#define BACKLOG 10
main()
{
// 在sock_fd 上進行監聽,new_fd 接受新的連接
int sockfd, new_fd ;
// 自己的地址信息
struct sockaddr_in my_addr;
// 連接者的地址信息
struct sockaddr_in their_addr;
int sin_size;
// 這裏就是我們一直強調的錯誤檢查.如果調用socket() 出錯,則返回
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
// 輸出錯誤提示並退出
perror("socket");
exit(1);
}
// 主機字節順序
my_addr.sin_family = AF_INET;
// 網絡字節順序,短整型
my_addr.sin_port = htons(MYPORT);
// 將運行程序機器的IP 填充入s_addr
my_addr.sin_addr.s_addr = INADDR_ANY;
// 將此結構的其餘空間清零
bzero(&(my_addr.sin_zero), 8);
// 這裏是我們一直強調的錯誤檢查!!
if (bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr)) == -1)
{
// 如果調用bind()失敗,則給出錯誤提示,退出
perror("bind");
exit(1);
}
// 這裏是我們一直強調的錯誤檢查!!
if (listen(sockfd, BACKLOG) == -1)
{
// 如果調用listen 失敗,則給出錯誤提示,退出
perror("listen");
exit(1);
}
while(1)
{
// 這裏是主accept()循環
sin_size = sizeof(struct sockaddr_in);
// 這裏是我們一直強調的錯誤檢查!!
if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1)
{
// 如果調用accept()出現錯誤,則給出錯誤提示,進入下一個循環
perror("accept");
continue;
}
// 服務器給出出現連接的信息
printf("server: got connection from %s\n", inet_ntoa(their_addr.sin_addr));
// 這裏將建立一個子進程來和剛剛建立的套接字進行通訊
if (!fork())
{
// 這裏是子進程
// 這裏就是我們說的錯誤檢查!
if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
{
// 如果錯誤,則給出錯誤提示,然後關閉這個新連接,退出
perror("send");
close(new_fd);
exit(0);
}
// 關閉new_fd 代表的這個套接字連接
close(new_fd);
}
}
// 等待所有的子進程都退出
while(waitpid(-1,NULL,WNOHANG) > 0);
}
###################################################################################
###################################################################################
/* include fig01 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/poll.h>
#include <errno.h>
#define MAXLINE 512
#define NOTDEF
int
main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(4563);
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listenfd, 12);
maxfd = listenfd; /* initialize */
maxi = -1; /* index into client[] array */
//for (i = 0; i < FD_SETSIZE; i++)
for (i = 0; i < 3; i++)
client[i] = -1; /* -1 indicates available entry */
/*
void FD_ZERO(fd_set *fdset)
Initialises the file descriptor set fdset to have zero bits for all file descriptors.
初始化所有的文件描述符fd_set爲0
*/
FD_ZERO(&allset);
/*
void FD_SET(int fd, fd_set *fdset)
Sets the bit for the file descriptor fd in the file descriptor set fdset.
*/
FD_SET(listenfd, &allset);
/* end fig01 */
/* include fig02 */
for ( ; ; ) {
rset = allset; /* structure assignment */
nready =select(maxfd+1, &rset, NULL, NULL, NULL);
/*
定義函數 int select(int n,fd_set * readfds,fd_set * writefds,fd_set *
exceptfds,struct timeval * timeout);
select()用來等待文件描述詞狀態的改變。參數n代表最大的文件描述詞加1,參數readfds、writefds 和exceptfds 稱爲描述詞組,是用來回傳該描述詞的讀,寫或例外的狀況。底下的宏提供了處理這三種描述詞組的方式:
FD_CLR(inr fd,fd_set* set);用來清除描述詞組set中相關fd 的位
FD_ISSET(int fd,fd_set *set);用來測試描述詞組set中相關fd 的位是否爲真
FD_SET(int fd,fd_set*set);用來設置描述詞組set中相關fd的位
FD_ZERO(fd_set *set); 用來清除描述詞組set的全部位
參數 timeout爲結構timeval,用來設置select()的等待時間,其結構定義如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
返回值 如果參數timeout設爲NULL則表示select()沒有timeout。
錯誤代碼 執行成功則返回文件描述詞狀態已改變的個數,如果返回0代表在描述詞狀態改變前已超過timeout時間,當有錯誤發生時則返回-1,錯誤原因存於errno,此時參數readfds,writefds,exceptfds和timeout的值變成不可預測。
EBADF 文件描述詞爲無效的或該文件已關閉
EINTR 此調用被信號所中斷
EINVAL 參數n 爲負值。
ENOMEM 核心內存不足
常見的程序片段:fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset){……}
*/
if (FD_ISSET(listenfd, &rset)) { /* new client connection */
/*
int FD_ISSET(int fd, fd_set *fdset)
Returns a non-zero value if the bit for the file descriptor fd is set in the file descriptor set by fdset, and 0 otherwise.
*/
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
printf("Welcome!\n");
sleep(1);
#ifndef NOTDEF
printf("new client: %s, port %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr, NULL,4 ),ntohs(cliaddr.sin_port));
#endif
//for (i = 0; i < FD_SETSIZE; i++)
for (i = 0; i < 3; i++)
if (client[i] < 0) {
client[i] = connfd; /* save descriptor */
break;
}
if (i == FD_SETSIZE)
printf("too many clients");
FD_SET(connfd, &allset); /* add new descriptor to set */
if (connfd > maxfd)
maxfd = connfd; /* for select */
if (i > maxi)
maxi = i; /* max index in client[] array */
if (--nready <= 0)
continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = read(sockfd, buf, MAXLINE)) == 0) {
/*4connection closed by client */
close(sockfd);
FD_CLR(sockfd, &allset);
/*
void FD_CLR(int fd, fd_set *fdset)
Clears the bit for the file descriptor fd in the file descriptor set fdset.
*/
client[i] = -1;
} else
write(sockfd, buf, n);
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
}
/* end fig02 */
###################################################################################