用C语言编写Socket程序.docx
《用C语言编写Socket程序.docx》由会员分享,可在线阅读,更多相关《用C语言编写Socket程序.docx(42页珍藏版)》请在冰豆网上搜索。
用C语言编写Socket程序
用C语言编写Socket程序
本文的目的在于为初学者提供一个快速的入门指导,用来迅速熟悉用C语言来编写
Internet网络应用程序。
本文假设读者已经具备了C语言的基本知识和语法,并且
读者有使用Uinx/Linux的经验。
尽管Uinx/Linux的Socket编程与在Windows下的有
一些不同的地方,但是在此我并不想展开。
另外,本文所有的程序都在RedHat
5.2下编译通过,并且在glibc2.0.7和libc5.3.12两种环境下都没有问题。
现在
就开始我们的教程吧:
)。
对一个程序员而言,sockets和底层的文件描述符非常类似(可以在sockets里使
用read()和write()函数),尽管建立一个socket比打开,读取和写入一个文件更
为麻烦,但这是由于网络连接比单纯的本地硬盘的读写复杂的多所造成的。
通常,sockets用来实现客户机/服务器对。
服务器的任务是监听某个特定的端口
,当接收到客户端的服务请求时完成相应的服务;客户机的任务是请求服务器完
成事先设定好的服务。
作为入门级的文章,我们在这里不会使用所有的socket类型和功能,但是我们会
向读者提供足够的信息。
现在,就让我们开始吧。
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
建立一个socket:
socket()
你所要学的socket编程的第一件事就是用socket()建立一个socket:
--------
#include
#include
intsocket(intaf,inttype,intprotocol)
-------
'intaf'代表地址族或者称为socket所代表的域,通常有两个选项:
AF_UNIX-只在单机上使用。
AF_INET-可以在单机或其他使用DARPA协议(UDP/TCP/IP)的异种机通信。
'inttype'代表你所使用的连接类型,通常也有两种情况:
SOCK_STREAM-用来建立面向连接的sockets,可以进行可靠无误的的数据传
输
SOCK_DGRAM-用来建立没有连接的sockets,不能保证数据传输的可靠性。
在本文中,我们着重使用AF_INET地址族和SOCK_STREAM连接类型。
'intprotocol'通常设定为0。
这样的目的是使系统选择默认的由协议族和连接类
型所确定的协议。
这个函数的返回值是一个文件描述句柄,如果在此期间发生错误则返回-1并且设
定了相应的errno。
-------
#include
#include
intsockfd/*soontobesocketfiledescriptor*/
sockfd=socket(AF_INET,SOCK_STREAM,0)
/*errorcheckinghere*/
-------
如果执行成功,我们就拥有了一个socket的文件句柄,通过这个句柄就可以访问
Internet了。
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
名字绑定socket:
bind()
下一步要完成的就是名字绑定工作了:
-------
#include
#include
intbind(intsockfd,structsockaddr*name,intnamelen)
-------
在这个函数里,sockfd是从socket()调用得到的文件描述句柄。
name是一个指向
sockaddr类型结构的一个指针。
如果地址族被设定为AF_UNIX,这个类型的定义是
如下所示:
-------
structsockaddr{
u_shortsa_family;
charsa_data[14];
};
-------
在这个结构种,name.sa_family应当被设定为AF_UNIX。
name.sa_data应当包含最
长为14个字节的文件名,这个文件名用来分配给socket。
namelen给出了文件名的
具体长度。
-------
#include
#include
structsockaddrname;
intsockfd;
name.sa_family=AF_UNIX;
strcpy(name.sa_data,"/tmp/whatever");
sockfd=socket(AF_UNIX,SOCK_STREAM,0)
/*errorcheckingcodehere*/
bind(sockfd,&name,strlen(name.sa_data)+sizeof(name.sa_family)
/*errorcheckingcodehere*/
-------
如果调用成功,则返回值为0,如果调用失败返回值为-1,并设定相应的错误代码
errno。
现在,让我们在使用另一种结构,它是在使用AF_INET地址族的时候使用的。
------
structsockaddr_in{
shortintsin_family;/*Addressfamily*/
unsignedshortintsin_port;/*Portnumber*/
structin_addrsin_addr;/*Internetaddress*/
unsignedcharsin_zero[8];/*Samesizeasstructsockaddr*/
};
-------
看起来它比前一个大多了,但要掌握它并不十分困难。
-------
#include
#include
#include
#include
intsockfd,port=23;
structsockaddr_inmy_addr;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
printf("SocketError,%d\n",errno);
exit
(1);
}
my_addr.sin_family=AF_INET;/*hostbyteorder*/
my_addr.sin_port=htons(port);/*seemanhtonsformoreinformation
*/
my_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*getouraddress*/
bzero(&(my_addr.sin_zero),8);/*zeroouttherestofthespace*/
if((bind(sockfd,(structsockaddr*)&my_addr,sizeof(structsockaddr))
==-1)
{
printf("BindError,%d\n",errno);
close(sockfd);
exit
(1);
}
-------
现在,如果没有问题的话,我们建立的socket就有一个名字了!
相反,如果不成
功,它会设定相应的错误代码,并使程序退出。
这里需要说明的是,如果你的计
算机不想和别人的计算机连接,那么完全没有必要使用bind()。
对于端口的绑定
,在服务器而言是不合适的,它只应该在客户机上实现。
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
远程连接:
connect()
这是和别的计算机连接所必须的一步。
-------
#include
#include
intconnect(intsockfd,structsockaddr*serv_addr,intaddrlen);
-------
sockfd是我们建立的文件描述句柄,serv_addr是一个sockaddr结构,包含目的的
地址和端口号,addrlen被设定为sockaddr结构的大小。
-------
#include
#include
#include
#defineDEST_IP"132.241.5.10"
#defineDEST_PORT23
main()
{
intsockfd;
structsockaddr_indest_addr;/*willholdthedestinationaddr*/
sockfd=socket(AF_INET,SOCK_STREAM,0);/*dosomeerrorchecking!
*
/
dest_addr.sin_family=AF_INET;/*hostbyteorder*/
dest_addr.sin_port=htons(DEST_PORT);/*short,networkbyteorder*/
dest_addr.sin_addr.s_addr=inet_addr(DEST_IP);
bzero(&(dest_addr.sin_zero),8);/*zerotherestofthestruct*/
connect(sockfd,(structsockaddr*)&dest_addr,sizeof(structsockaddr)
);
/*errorcheckingcodehere*/
/*morecode
.
.
.
*/
}
-------
同样,connect()在调用返回后,如果返回值为0则表明成功,如果是1则说明有错
误,并且同时设定了相应的错误代码。
由于我们现在不关心具体和那个端口连接
,所以在上面的例程里我们没有调用了bind()函数。
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
监听:
listen()
当我们需要建立一个服务器的时候,我们需要有一种手段来监听输入的请求,而
listen()函数正是提供这个功能。
-------
#include
#include
intlisten(intsockfd,intbacklog);
-------
参数backlog是指一次可以监听多少个连接
它的调用返回结果和上述的几个函数是一样的,这里就不多说了。
值得一提的是,在这里,我们需要建立一个绑定,用来接收特定端口的服务请求
。
-------
socket();/*tocreateoutsocketfiledescriptor*/
bind();/*togiveoursocketaname*/
listen();/*listenforconnection*/
-------
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
接受连接:
accept()
好了,现在开始实质性的工作了。
当有人试图从我们打开的端口登陆进来时我们
应该响应他,这个时候就要用到accept()函数了。
-------
#include
#include
intaccept(intsockfd,void*addr,int*addrlen);
-------
函数调用所需的参数都是我们所熟悉的:
)
-------
#include
#include
#include
#defineMYPORT1500/*theportuserswillbeconnectingto*/
#defineBACKLOG5/*howmanypendingconnectionsqueuewillhold*/
main()
{
intsockfd,new_fd;/*listenonsock_fd,newconnectiononnew_fd*/
structsockaddr_inmy_addr;/*myaddressinformation*/
structsockaddr_intheir_addr;/*connector'saddressinformation*/
intsin_size;
sockfd=socket(AF_INET,SOCK_STREAM,0);/*dosomeerrorchecking!
*
/
my_addr.sin_family=AF_INET;/*hostbyteorder*/
my_addr.sin_port=htons(MYPORT);/*short,networkbyteorder*/
my_addr.sin_addr.s_addr=INADDR_ANY;/*auto-fillwithmyIP*/
bzero(&(my_addr.sin_zero),8);/*zerotherestofthestruct*/
/*didyourememberyourerrorchecking?
*/
bind(sockfd,(structsockaddr*)&my_addr,sizeof(structsockaddr));
listen(sockfd,BACKLOG);
sin_size=sizeof(structsockaddr_in);
new_fd=accept(sockfd,&their_addr,&sin_size);
-------
这里我们要注意的是:
我们用new_fd来完成所有的接收和发送的操作。
如果在只
有一个连接的情况下你可以关闭原来的sockfd用来防止更多的输入请求。
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
输入和输入的完成:
send()andrecv()
在我们完成了上述的工作后,最后一步就是传输数据了:
)。
在这里,我们通过se
nd()和recv()来实现。
-------
#include
#include
intsend(intsockfd,constvoid*msg,intlen,intflags);
intrecv(intsockfd,void*buf,intlen,unsignedintflags);
-------
send():
sockfd-socketfiledescriptor
msg-messagetosend
len-sizeofmessagetosend
flags-read'mansend'formoreinfo,setitto0fornow:
)
recv():
sockfd-socketfiledescriptor
buf-datatoreceive
len-sizeofbuf
flags-sameasflagsinsend()
send()例程:
-------
char*msg="Heytherepeople";
intlen,send_msg;
/*codetocreate(),bind(),listen()andaccept()*/
len=strlen(msg);
bytes_sent=send(sockfd,msg,len,0);
-------
recv()例程:
-------
char*buf;
intlen,recv_msg;
/*codetocreate(),bind(),listen()andaccept()*/
len=strlen(buf);
recv_msg=recv(sockfd,buf,len,0);
-------
如果你使用的连接类型是SOCK_DGRAM,那么应该使用sendto()和recvfrom()来实
现数据传输。
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
最后一步:
close()andshutdown()
当传输结束时,应当关闭连接。
-------
#include
/*allyoucode*/
close(sockfd);
-------
更保险的方法是用shutdown()来关闭连接。
-------
intshutdown(intsockfd,inthow)
-------
参数how的选择:
1-不允许接收更多的数据
2-不允许发送更多的数据
3-不允许接收和发送更多的数据(和close()一样)
一切就是这么简单:
)
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
你是谁:
getpeerbyname()
可能你还想知道是谁正在和你连接,那么看下面:
-------
#include
intgetpeername(intsockfd,structsockaddr*addr,int*addrlen);
-------
参数addr是一个指向'structsockaddr'或者'structsockaddr_in'的指针。
如果执行成功,我们就得到了对方的地址了,然后用inet_ntoa()用gethostbyad
dr()来得到对方更多的信息。
如果还想知道更多的,请参阅RFC1413。
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
我是谁:
gethostname()
-------
#include
intgethostname(char*hostname,size_tsize);
-------
hostname是一个存放主机名字的字符数组
返回的hostname可以作为gethostbyname()的参数,这样又可以得到自己的IP地址
了。
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
我的IP是多少?
现在把我们所学的集中起来,逐步就可以完成一个实用的程序了。
还是来研究一
下如下的情况:
$telnet
Trying206.163.24.176(nottherealaddressbutI'mtoolazytotry:
)
)
我们看到,telnet程序所做的第一件事情是域名解析!
这个工作是由gethostbyn
ame()完成的。
-------
#include
structhostent*gethostbyname(constchar*name);
-------
一个特殊的结构hostent:
-------
structhostent{
char*h_name;
char**h_aliases;
inth_addrtype;
inth_length;
char**h_addr_list;
};
#defineh_addrh_addr_list[0]
-------
这个结构可以被分为:
h_name-正式的机器名
h_aliases-一个以NULL结尾的字符串,表示机器的别名。
h_addrtype-地址所使用的类型,通常是AF_INET。
h_length-用字节数表示的地址长度。
h_addr_list-一个以0结尾的数组,表示机器的网络地址,以网络的字节顺序排
列。
h_addr-在h_addr_list里的第一个地址。
gethostbyname()返回一个指向hostent结构的指针或者是一个NULL指针表示错误
(尽管如此,错误代码没有设定!
)。
现在我们就来完成我们的DNS程序:
-------
#include
#include
#include
#include
#include
#include
intmain(intargc,char*argv[])
{
structhostent*h;
if(argc!
=2){/*errorcheckingonthecommandline*/
fprintf(stderr,"Usage:
getip\n");
exit
(1);
}
if((h=gethostbyname(argv[1]))==NULL){/*getthehostinfo*/
herror("gethostbyname");
exit
(1);
}
printf("Hostname:
%s\n",h->h_name);
printf("IPAddres