套接字是一種通訊機制,憑藉這種機制,客戶/服務器系統的開發工做既能夠在本地單機上進行,也能夠跨網絡進行,Linux所提供的功能(如打印服 務,ftp等)一般都是經過套接字來進行通訊的,套接字的建立和使用與管道是有區別的,由於套接字明確地將客戶和服務器區分出來,套接字能夠實現將多個客 戶鏈接到一個服務器。數據庫
套接字屬性ubuntu
套接字的特性由3個屬性肯定,他們是,域,類型和協議vim
域指定套接字通訊中使用的網絡介質,最多見的套接字域是AF_INET,它指的是Internet網絡服務器
套接字類型網絡
一個套接字可能有多種不一樣的通訊方式數據結構
流套接字,流套接字提供一個有序,可靠,雙向節流的連接,流套接字由類型SOCK_STREAM指定,它是在AF_INET域中經過TCP/IP連接實現的,這就是套接字類型(其實就是通訊方式)dom
與流套接字相反,由類型SOCK_DGRAM指定的數據報套接字不創建和維持一個鏈接,它對能夠發送的數據長度有限制,數據報做爲一個單獨的網絡消息被傳輸,它可能會丟失,複製或亂序socket
最後一個是套接字協議,一般使用默認就能夠了(也就是最後一個參數填0)tcp
建立套接字ide
socket系統調用建立一個套接字並返回一個描述符,該描述符能夠用來訪問該套接字
#include
#include
int socket(int domain,int type,int protocol);
建立的套接字是一條通訊線路的一個端點,domain參數指定協議族(使用的網絡介質),type參數指定這個套接字的通訊類型(通訊方式),protocot參數指定使用的協議
domain參數能夠指定以下協議族
AF_UNIX UNIX域協議(文件系統套接字)
AF_INET ARPA因特網協議
AF_ISSO ISO標準協議
AF_NS Xerox網絡協議
AF_IPX Novell IPX協議
AF_APPLETALK Appletalk DDS協議
最經常使用的套接字域是AF_UNIX和AF_INET,前者用於經過UNIX和Linux文件系統實現本地套接字
socket函數的第二個參數type指定用於新套接字的特性,它的取值包括SOCK_STREAM和SOCK_DGRAM
SOCK_STREAM是一個有序,可靠,面向鏈接的雙向字節流,通常用這個
最後一個protocol參數,將參數設爲0表示使用默認協議。
套接字地址
每一個套接字(端點)都有其本身的地址格式,對於AF_UNIX套接字來講,它的地址由結構sockaddr_un來描述,該結構體定義在頭文件sys/un.h中,以下:
struct sockaddr_un {
sa_family_t sun_family; //套接字域
char sun_path[];//名字
};
而在AF_INET域中,套接字地址結構由sockaddr_in來指定,該結構體定義在頭文件netinet/in.h中
struct sockaddr_in {
short int sin_family;//套接字域
unsigned short int sin_port;//接口
struct in_addr sin_addr;
}
IP地址結構in_addr被定義以下:
struct in_addr {
unsigned long int s_addr;
};
命名套接字
要想讓經過socket調用建立的套接字能夠被其它進程使用,服務器程序就必須給該套接字命名,以下,AF_INET套接字就會關聯到一個IP端口號
#include
int bind(int socket,const struct sockaddr *address,size_t address_len);
bind系統調用把參數address中的地址分配給與文件描述符socket關聯的未命名套接字
建立套接字隊列
爲了可以在套接字上接受進入連接,服務器程序必須建立一個隊列來保存未處理的請求,它用listen系統調用來完成這一工做
#include
int listen(int socket,int backlog);
Linux系統可能會對隊列中未處理的鏈接的最大數目作出限制
接受鏈接
一旦服務器程序建立並命名套接字以後,他就能夠經過accept系統調用來等待客戶創建對該套接字的鏈接
#include
int accept(int socket,struct sockaddr *address,size_t *address_len);
accept函數將建立一個新套接字來與該客戶進行通訊,而且返回新套接字描述符(這個描述符和客戶端中描述符是同樣等同)
請求鏈接
客戶程序經過一個未命名套接字和服務器監聽套接字之間創建的鏈接的方法來鏈接到服務器,以下:
#include
int connect(int socket,const struct sockaddr *address,size_t address_len);
參數socket指定的套接字將鏈接到參數address指定的服務器套接字
關閉套接字
你能夠經過調用close函數來終止服務器和客戶上的套接字鏈接
套接字通訊
套接字能夠經過調用read(),write()系統調用來進行傳輸數據
下面是套接字的一些例子
一個簡單的本地客戶
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd;
int len;
struct sockaddr_un address;//套接字地址
int result;
char ch = 'A';
sockfd = socket(AF_UNIX,SOCK_STREAM,0);//建立一個套接字(端點),並返回一個描述符
address.sun_family = AF_UNIX;//指明網絡介質
strcpy(address.sun_path,"server_socket");// 名字
len = sizeof(address);
result = connect(sockfd,(struct sockaddr *)&address,len);//請求鏈接到address
if(result == -1) {
perror("oops: clientl");
exit(1);
}
write(sockfd,&ch,1);//把數據寫入套接字
read(sockfd,&ch,1);//服務器處理後讀出處理後數據
printf("char form server = %c\n",ch);
close(sockfd);
exit(0);
}
下面是一個本地服務器
#include
#include
#include
#include
#include
#include
int main()
{
int server_sockfd,client_sockfd;//定義套接字描述符
int server_len,client_len;
struct sockaddr_un server_address;//套接字地址
struct sockaddr_un client_address;//套接字地址
unlink("server_socket");
server_sockfd = socket(AF_UNIX,SOCK_STREAM,0);//建立一個套接字,並返回一個描述符
server_address.sun_family = AF_UNIX;//指定網絡介質
strcpy(server_address.sun_path,"server_socket");//名字
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字
listen(server_sockfd,5);//建立套接字隊列
while(1) {
char ch;
printf("server waiting\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接受鏈接
read(client_sockfd,&ch,1);//從套接字中讀取數據
ch++;// 處理數據
write(client_sockfd,&ch,1);//把數據從新寫回套接字
close(client_sockfd);
}
}
這兩個程序運行以下:
root@www:/opt/chengxu# ./server1 &
[3] 4644
root@www:/opt/chengxu# server waiting
root@www:/opt/chengxu# ./client1 & ./client1 & ./client1 &
[4] 4652
[5] 4653
[6] 4654
root@www:/opt/chengxu# server waiting
server waiting
server waiting
char form server = B
char form server = B
char form server = B
[4] Done ./client1
[5]- Done ./client1
[6]+ Done
下面看一個網絡套接字的例子,先看server程序:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int server_sockfd,client_sockfd;//定義套接字描述符
int server_len,client_len;
struct sockaddr_in server_address;//套接字地址結構體
struct sockaddr_in client_address;//套接字地址結構體
unlink("server_socket");
server_sockfd = socket(AF_INET,SOCK_STREAM,0);//建立一個套接字,並返回一個描述符
server_address.sin_family = AF_INET;//指定網絡介質
//server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
server_address.sin_addr.s_addr = htonl(INADDR_ANY);//客戶鏈接到服務器的IP
server_address.sin_port = htons(9734);//客戶鏈接到端口
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字
listen(server_sockfd,5);//建立套接字隊列
while(1) {
char ch;
printf("server waiting\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接受鏈接
read(client_sockfd,&ch,1);//從套接字中讀取數據
ch++;//處理數據
write(client_sockfd,&ch,1);//把處理後數據寫入套接字
close(client_sockfd);//關閉套接字
}
}
下面是客戶端程序:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd;//套接字描述符
int len;
struct sockaddr_in address;//套接字地址結構體
int result;
char ch = 'A';
sockfd = socket(AF_INET,SOCK_STREAM,0);//建立一個套接字並返回一個描述符
address.sin_family = AF_INET;// 指定網絡介質
address.sin_addr.s_addr = htonl(INADDR_ANY);//要鏈接到主機IP
address.sin_port = htons(9734);//要鏈接到端口號
len = sizeof(address);
result = connect(sockfd,(struct sockaddr *)&address,len);//請求鏈接
if(result == -1) {
perror("oops: clientl");
exit(1);
}
write(sockfd,&ch,1);//把數據寫入套接字
read(sockfd,&ch,1);//服務器處理完數據後重新讀取出來
printf("char form server = %c\n",ch);
close(sockfd);
exit(0);
}
運行這兩個程序輸出以下:
root@www:/opt/chengxu# ./server2 &
[4] 4746
root@www:/opt/chengxu# server waiting
root@www:/opt/chengxu# ./client2 & ./client2 & ./client2 &
[5] 4749
[6] 4750
[7] 4751
root@www:/opt/chengxu# server waiting
server waiting
server waiting
char form server = B
char form server = B
char form server = B
[5] Done ./client2
[6]- Done ./client2
[7]+ Done ./client2
root@www:/opt/chengxu#
輸出結果基本和上面的例子差很少,經過上面程序能夠發現客戶端寫入到套接字中的數據能夠從服務器中讀出來,並且客戶端會等待服務器把數據讀出處理後再把數據讀回來,這有一個順序,並不會出現亂序,有點類型於管道通訊並且是雙向的
主機字節序和網絡字節序
經過套接字接口傳遞的端口號和地址都是二進制的,不一樣計算機使用不一樣的字節序來表示整數,如32位的整數分爲4個連續的字節,並以1-2-3-4存在內存中,這裏的1表示最高位,也即大端模式,而有的處理器是以4-3-2-1存取的,兩個不一樣的計算機獲得的整數就會不一致
爲了使不一樣類型的計算機能夠就經過網絡傳輸多字節的值達成一致,客戶和服務器程序必須在傳出以前,將它們內部整數表示方式轉換爲網絡字節序,它們經過下面函數轉換(也就是把端口號等轉換成統一網絡字節序)
#include
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);
這些函數將16位和32位整數在主機字節序和標準的網絡字節序之間進行轉換,如上面例子中用到的
address.sin_addr.s_addr = htonl(INADDR_ANY);//要鏈接到主機IP
address.sin_port = htons(9734);//要鏈接到端口號
這樣就能保證網絡字節序的正確,若是你使用的計算機上的主機字節序和網絡字節序相同,將看不到任何差別
網絡信息
到目前爲止,咱們客戶和服務器程序一直是把地址和端口編譯到它們本身的內部,對於一個更通用的服務器和客戶程序來講,咱們能夠經過網絡信息函數來決定應該使用的地址和端口(也就是說能夠經過網絡信息函數來獲取IP和端口號等信息)
相似的,若是給定一個計算機名字,你能夠經過調用解析地址的主機數據庫函數來肯定它的IP地址等信息
主機數據庫函數在接口文件netdb.h中聲明,以下:
#include <netdb.h>
struct hostent *gethostbyaddr(const void *addr,size_t len,int type);
struct hostent *gethostbyname(const char *name);//得到計算機主機數據庫信息
這些函數返回的結構體中至少包括如下幾個成員
struct hostent {
char *h_name; //name of the host
char **h_aliases;//list of aliases
int h_addrtypr;//address type
int h_length;//length in bytes of the address
char **h_addr_list;//list of address
};
若是沒有與咱們查詢的主機或地址相關的數據項,這些信息函數將返回一個空指針
類型地,與服務及相關聯端口號有關的信息也能夠經過一些服務信息函數來獲取
#include <netdb.h>
struct servent *getservbyname(const char *name,const char *proto);//檢查是否有某個服務
struct servent *getservbyport(int port,const char *proto);
proto參數指定用於鏈接該服務的協議,它的兩個選項tcp和udp,前者用於SOCK_STREAM類型
返回結構體中至少包含以下幾個成員:
struct servent {
char *s_name;//name of the service
char **s_aliases;//list of aliases
int s_port;//The IP port number
char *s_proto;//The service type,usually "tcp" or "udp"
};
若是想獲取某臺計算機主機數據庫信息,能夠調用gethostbyname函數而且將結果打印出來,注意,要把返回的地址錶轉換爲正確的地址類型,並用函數inet_ntoa將它們從網絡字節序裝換爲可打印的字符串,以下:
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
這個函數的做用是將一個因特網主機地址轉換爲一個點分四元租格式的字符串
下面這個程序getname.c用來獲取一臺主機有關的信息,以下:
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
char *host,**names,**addrs;//接收用到的一些指針
struct hostent *hostinfo;//指向gethostbyname函數返回的結構體指針
if(argc == 1) {
char myname[256];
gethostname(myname,255);
host = myname;
}
else
host = argv[1];//獲取主機名
hostinfo = gethostbyname(host);//獲取主機數據庫
if(!hostinfo) {
fprintf(stderr,"cannot get info for host: %s\n",host);
exit(1);
}
printf("results for host %s:\n",host);
printf("Name: %s\n",hostinfo -> h_name);
printf("Aliases");
names = hostinfo -> h_aliases;
while(*names) {
printf(" %s",*names);
names++;
}
printf("\n");
if(hostinfo -> h_addrtype != AF_INET) {
fprintf(stderr,"not an IP host!\n");
exit(1);
}
//顯示主機的全部IP地址
addrs = hostinfo -> h_addr_list;
while(*addrs) {
printf(" %s",inet_ntoa(*(struct in_addr *)*addrs));
addrs++;
}
printf("\n");
exit(0);
}
運行這個程序輸出以下所示:
root@www:/opt/chengxu# ./getname
results for host www:
Name: www.kugoo.com
Aliases www
127.0.1.1
root@www:/opt/chengxu# ./getname localhost
results for host localhost:
Name: localhost
Aliases ip6-localhost ip6-loopback
127.0.0.1 127.0.0.1
root@www:/opt/chengxu#
下面一個例子是鏈接到標準服務
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char *argv[])//argc表示參數個數,argv[]表示具體參數程序名爲argv[0]
{
char *host;
int sockfd;
int len,result;
struct sockaddr_in address;//套接字地址結構體
struct hostent *hostinfo;//指向gethostbyname函數返回的結構體指針
struct servent *servinfo;//指向getservbyname函數返回的結構體指針
char buffer[128];
if(argc == 1)
host = "localhost";
else
host = argv[1];
hostinfo = gethostbyname(host);//獲取主機數據庫信息
if(!hostinfo) {
fprintf(stderr,"no host: %s\n",host);
exit(1);
}
//檢查主機上是否有daytime服務
servinfo = getservbyname("daytime","tcp");
if(!servinfo) {
fprintf(stderr,"no daytime service\n");
exit(1);
}
sockfd = socket(AF_INET,SOCK_STREAM,0);//建立一個套接字,並返回一個描述符
address.sin_family = AF_INET;//使用網絡介質
address.sin_port = servinfo -> s_port;//端口號
address.sin_addr = *(struct in_addr *)*hostinfo -> h_addr_list;//獲取主機IP地址
len = sizeof(address);
result = connect(sockfd,(struct sockaddr *)&address,len);//請求鏈接
if(result == -1) {
perror("oops:getdate");
exit(1);
}
result = read(sockfd,buffer,sizeof(buffer));
buffer[result] = '\0';
printf("read %d bytes: %s",result,buffer);
close(sockfd);
exit(0);
}
運行這個程序輸出以下:
root@www:/opt/chengxu# ./getname1 localhost
oops:getdate: Connection refused
root@www:/opt/chengxu#
之因此這樣是由於個人虛擬機中沒有啓動daytime這個服務
我用的是ubuntu虛擬機,下面來啓動這個daytime服務看一下
首先
root@www:/opt/chengxu# vim /etc/inetd.conf
進入到inetd的配置文件把這個服務前面的#號去掉,改爲以下:
18 #discard dgram udp wait root internal
19 daytime stream tcp nowait root internal
20 #time stream tcp nowait root internal
而後保存退出
接下來重啓一下服務就能夠了經過下面命令啓動(或重啓)xinetd服務(xinetd和openbsd-inetd差很少都是屬於守護進程)
root@www:/opt/chengxu# /etc/init.d/openbsd-inetd restart
* Restarting internet superserver inetd [ OK ]
root@www:/opt/chengxu#
能夠經過下面命令看一下daytime這個服務是否處於監聽狀態了,以下:
root@www:/opt/chengxu# netstat -a | grep daytime
tcp 0 0 *:daytime *:* LISTEN
root@www:/opt/chengxu#
下面再從新運行一下上面程序看看:
root@www:/opt/chengxu# ./getname1 localhost
read 26 bytes: Sun Sep 23 23:15:14 2012
root@www:/opt/chengxu#
因特網守護進程
當有客戶端鏈接到某個服務時,守護進程就運行相應的服務器,這使得針對各項網絡服務器不須要移植運行着,咱們一般是經過一個圖形界面來配置xinetd,ubuntu用的好像是openbsd-inetd這 個守護進程,咱們能夠直接修改它的配置文件,ubuntu對應的是/etc/inetd.conf這個文件,就拿咱們上面那個daytime這個服務爲 例,在ubuntu中它默認是關閉的,這時咱們要進入它的配置文件裏,把它前面的#字符去掉就能夠了,修改了的配置文件以下:
17 #discard stream tcp nowait root internal
18 #discard dgram udp wait root internal
19 daytime stream tcp nowait root internal
20 #time stream tcp nowait root internal
21
22 #:STANDARD: These are standard services.
23
24 #:BSD: Shell, login, exec and talk are BSD protocols.
25
26 #:MAIL: Mail, news and uucp services.
27
28 #:INFO: Info services
29
30 #:BOOT: TFTP service is provided primarily for booting. Most sites
31 # run this only on machines acting as "boot servers."
32 bootps dgram udp wait root /usr/sbin/bootpd bootpd -i -t 120
33 tftp dgram udp wait nobody /usr/sbin/tcpd /usr/sbin/in.tftpd /srv/tftproot
34 #bootps dgram udp wait root /usr/sbin/bootpd bootpd -i -t 120