套接字是一种通讯机制,凭借这种机制,客户/服务器系统的开发工做既能够在本地单机上进行,也能够跨网络进行,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