linux系列(一)C语言编写Socket程序

用C语言编写Socket程序---入门篇 



本文的目的在于为初学者提供一个快速的入门指导,用来迅速熟悉用C语言来编写

Internet
网络应用程序。本文假设读者已经具备了C语言的基本知识和语法,并且读者有使用Uinx/Linux的经验。尽管Uinx/Linux的Socket编程
与在Windows下的有一些不同的地方,但是在此我并不想展开。另外,本文所有的程序都在Red Hat 5.2下编译通过,并且在
glibc 2.0.7和libc 5.3.12两种环境下都没有问题。现在就开始我们的教程吧:)。 

对一个程序员而言,sockets和底层的文件描述符非常类似(可以在sockets里使用read()和write()函数),尽管建立一个socket比打开,读取和写入一个文件更为麻烦,但这是由于网络连接比单纯的本地硬盘的读写复杂的多所造成的。 

通常,sockets用来实现客户机/服务器对。服务器的任务是监听某个特定的端口,当接收到客户端的服务请求时完成相应的服务;客户机的任务是请求服务器完成事先设定好的服务。 

作为入门级的文章,我们在这里不会使用所有的socket类型和功能,但是我们会向读者提供足够的信息。现在,就让我们开始吧。 

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+= 
建立一个socket:socket() 



你所要学的socket编程的第一件事就是用socket()建立一个socket: 



- ------- 



#include <sys/types.h> 

#include <sys/socket.h> 



int socket(int af, int type, int protocol) 



- ------ 



'int af'代表地址族或者称为socket所代表的域,通常有两个选项: 

    AF_UNIX - 只在单机上使用。 

    AF_INET - 可以在单机或其他使用DARPA协议(UDP/TCP/IP)的异种机通信。 





'int type'代表你所使用的连接类型,通常也有两种情况: 

    SOCK_STREAM - 用来建立面向连接的sockets,可以进行可靠无误的的数据传

输 

    SOCK_DGRAM - 用来建立没有连接的sockets,不能保证数据传输的可靠性。





在本文中,我们着重使用AF_INET地址族和SOCK_STREAM连接类型。 



'int protocol'通常设定为0。这样的目的是使系统选择默认的由协议族和连接类型所确定的协议。 



这个函数的返回值是一个文件描述句柄,如果在此期间发生错误则返回-1并且设定了相应的errno。 



- ------ 



#include <sys/types.h> 

#include <sys/socket.h> 



int sockfd /* soon to be socket file descriptor */ 



sockfd = socket(AF_INET, SOCK_STREAM, 0) 

/* error checking here */ 



- ------ 



如果执行成功,我们就拥有了一个socket的文件句柄,通过这个句柄就可以访问

Internet了。 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 

名字绑定socket: bind() 



下一步要完成的就是名字绑定工作了: 



- ------ 



#include <sys/types.h> 

#include <sys/socket.h> 



int bind(int sockfd, struct sockaddr *name, int namelen) 



- ------ 



在这个函数里,sockfd是从socket()调用得到的文件描述句柄。name是一个指向

sockaddr类型结构的一个指针。如果地址族被设定为AF_UNIX,这个类型的定义是

如下所示: 



- ------ 



struct sockaddr {  

u_short sa_family; 

char sa_data[14]; 

}; 



- ------ 



在这个结构种,name.sa_family应当被设定为AF_UNIX。name.sa_data应当包含最

长为14个字节的文件名,这个文件名用来分配给socket。namelen给出了文件名的

具体长度。 



- ------ 



#include <sys/types.h> 

#include <sys.socket.h> 



struct sockaddr name; 

int sockfd; 



name.sa_family = AF_UNIX; 

strcpy(name.sa_data, "/tmp/whatever"); 



sockfd = socket(AF_UNIX, SOCK_STREAM, 0) 

/* error checking code here */ 



bind(sockfd, &name, strlen(name.sa_data) + sizeof(name.sa_family) 

/* error checking code here */ 



- ------ 



如果调用成功,则返回值为0,如果调用失败返回值为-1,并设定相应的错误代码errno。 



现在,让我们在使用另一种结构,它是在使用AF_INET地址族的时候使用的。 



- ----- 



struct sockaddr_in {  

short int sin_family; /* Address family */ 

unsigned short int sin_port; /* Port number */ 

struct in_addr sin_addr; /* Internet address */ 

unsigned char sin_zero[8]; /* Same size as struct sockaddr */ 

}; 



- ------ 



看起来它比前一个大多了,但要掌握它并不十分困难。 



- ------ 



#include <stdio.h> 

#include <sys/types.h> 

#include <sys/socket.h> 

#include <errno.h> 



int sockfd, port = 23; 

struct sockaddr_in my_addr; 



if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1) 

{  

printf("Socket Error, %d/n", errno); 

exit(1); 





my_addr.sin_family = AF_INET; /* host byte order */ 

my_addr.sin_port = htons(port); /* see man htons for more information 

*/ 

my_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* get our address */ 

bzero(&(my_addr.sin_zero), 8); /* zero out the rest of the space */ 



if((bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))

 == -1) 

{  

printf("Bind Error, %d/n", errno); 

close(sockfd); 

exit(1); 





- ------ 




在,如果没有问题的话,我们建立的socket就有一个名字了!相反,如果不成功,它会设定相应的错误代码,并使程序退出。这里需要说明的是,如果你的计
算机不想和别人的计算机连接,那么完全没有必要使用bind()。对于端口的绑定,在服务器而言是不合适的,它只应该在客户机上实现。 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 

远程连接: connect() 



这是和别的计算机连接所必须的一步。 



- ------ 



#include <sys/types.h> 

#include <sys/socket.h> 



int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);  



- ------ 



sockfd是我们建立的文件描述句柄,serv_addr是一个sockaddr结构,包含目的的

地址和端口号,addrlen 被设定为sockaddr结构的大小。 



- ------ 



#include <string.h>  

#include <sys/types.h>  

#include <sys/socket.h>  



#define DEST_IP "132.241.5.10" 

#define DEST_PORT 23 



main() 

{  

int sockfd; 

struct sockaddr_in dest_addr; /* will hold the destination addr */ 



sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! *





dest_addr.sin_family = AF_INET; /* host byte order */ 

dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */



dest_addr.sin_addr.s_addr = inet_addr(DEST_IP); 

bzero(&(dest_addr.sin_zero), 8); /* zero the rest of the struct */ 



connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)

); 

/* error checking code here */ 

/* more code  







*/ 





- ------ 



同样,connect()在调用返回后,如果返回值为0则表明成功,如果是1则说明有错误,并且同时设定了相应的错误代码。由于我们现在不关心具体和那个端口连接,所以在上面的例程里我们没有调用了bind()函数。 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 

监听: listen() 



当我们需要建立一个服务器的时候,我们需要有一种手段来监听输入的请求,而

listen()函数正是提供这个功能。 



- ------ 



#include <sys/types.h> 

#include <sys/socket.h> 



int listen(int sockfd, int backlog); 



- ------ 



参数backlog是指一次可以监听多少个连接 



它的调用返回结果和上述的几个函数是一样的,这里就不多说了。 

值得一提的是,在这里,我们需要建立一个绑定,用来接收特定端口的服务请求。 



- ------ 



socket(); /* to create out socket file descriptor */ 

bind(); /* to give our socket a name */ 

listen(); /* listen for connection */ 



- ------ 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 

接受连接: accept() 



好了,现在开始实质性的工作了。当有人试图从我们打开的端口登陆进来时我们应该响应他,这个时候就要用到accept()函数了。 



- ------ 



#include <sys/types.h> 

#include <sys/socket.h> 



int accept(int sockfd, void *addr, int *addrlen); 



- ------ 



函数调用所需的参数都是我们所熟悉的:) 



- ------ 



#include <string.h>  

#include <sys/types.h>  

#include <sys/socket.h>  



#define MYPORT 1500 /* the port users will be connecting to */ 

#define BACKLOG 5 /* how many pending connections queue will hold */ 



main() 

{  

int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */ 



struct sockaddr_in my_addr; /* my address information */ 

struct sockaddr_in their_addr; /* connector's address information */ 

int sin_size; 



sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! *





my_addr.sin_family = AF_INET; /* host byte order */ 

my_addr.sin_port = htons(MYPORT); /* short, network byte order */ 

my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */ 

bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */ 



/* did you remember your error checking? */ 

bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); 



listen(sockfd, BACKLOG); 



sin_size = sizeof(struct sockaddr_in); 

new_fd = accept(sockfd, &their_addr, &sin_size); 



- ------ 



这里我们要注意的是:我们用new_fd来完成所有的接收和发送的操作。如果在只有一个连接的情况下你可以关闭原来的sockfd用来防止更多的输入请求。 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 

输入和输入的完成: send() and recv() 



在我们完成了上述的工作后,最后一步就是传输数据了:)。在这里,我们通过send()和recv()来实现。 



- ------ 



#include <sys/types.h> 

#include <sys/socket.h> 



int send(int sockfd, const void *msg, int len, int flags); 

int recv(int sockfd, void *buf, int len, unsigned int flags); 



- ------ 

send(): 

sockfd - socket file descriptor 

msg - message to send 

len - size of message to send 

flags - read 'man send' for more info, set it to 0 for now :) 



recv(): 

sockfd - socket file descriptor 

buf - data to receive 

len - size of buf 

flags - same as flags in send() 



send() 例程: 

- ------ 



char *msg = "Hey there people"; 

int len, send_msg; 



/* code to create(), bind(), listen() and accept() */  



len = strlen(msg); 

bytes_sent = send(sockfd, msg, len, 0); 



- ------ 

recv() 例程: 

- ------ 



char *buf; 

int len, recv_msg; 



/* code to create(), bind(), listen() and accept() */ 



len = strlen(buf); 

recv_msg = recv(sockfd, buf, len, 0); 



- ------ 



如果你使用的连接类型是SOCK_DGRAM,那么应该使用sendto()和recvfrom()来实现数据传输。 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 

最后一步: close() and shutdown() 



当传输结束时,应当关闭连接。 



- ------ 



#include <stdio.h> 



/* all you code */ 



close(sockfd); 



- ------ 



更保险的方法是用shutdown()来关闭连接。 



- ------ 



int shutdown(int sockfd, int how) 



- ------ 



参数how的选择: 

1 - 不允许接收更多的数据 

2 - 不允许发送更多的数据 

3 - 不允许接收和发送更多的数据(和close()一样) 



一切就是这么简单:) 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 

你是谁: getpeerbyname() 



可能你还想知道是谁正在和你连接,那么看下面: 



- ------ 



#include <sys/socket.h> 



int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); 



- ------ 



参数addr是一个指向'struct sockaddr'或者'struct sockaddr_in'的指针。 



如果执行成功,我们就得到了对方的地址了,然后用inet_ntoa()用gethostbyad

dr()来得到对方更多的信息。如果还想知道更多的,请参阅RFC 1413。 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 

我是谁: gethostname() 



- ------ 



#include <unistd.h> 



int gethostname(char *hostname, size_t size); 



- ------ 

hostname是一个存放主机名字的字符数组 



返回的hostname可以作为gethostbyname()的参数,这样又可以得到自己的IP地址了。 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 

我的IP是多少?  



现在把我们所学的集中起来,逐步就可以完成一个实用的程序了。还是来研究一下如下的情况: 



$ telnet microsoft.com 

Trying 206.163.24.176 (not the real address but I'm too lazy to try :)





我们看到,telnet程序所做的第一件事情是域名解析!这个工作是由gethostbyname()完成的。 



- ------ 



#include <netdb.h>  



struct hostent *gethostbyname(const char *name); 



- ------ 



一个特殊的结构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] 



- ------ 

这个结构可以被分为: 



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 <stdio.h>  

#include <stdlib.h>  

#include <errno.h>  

#include <netdb.h>  

#include <sys/types.h> 

#include <netinet/in.h>  



int main(int argc, char *argv[]) 

{  

struct hostent *h; 



if (argc != 2) {  /* error checking on the command line */ 

fprintf(stderr,"Usage: getip <host name>/n"); 

exit(1); 





if ((h=gethostbyname(argv[1])) == NULL) {  /* get the host info */ 

herror("gethostbyname"); 

exit(1); 





printf("Host name : %s/n", h->h_name); 

printf("IP Address : %s/n",inet_ntoa(*((struct in_addr *)h->h_addr)));





return 0; 





- ------ 



用gcc -o getip getip.c就可以了完成了。 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 

客户机和服务器程序 



好了,让我们用一个小的客户机-服务器应用来结束本文的讨论。 



这个小程序的目的就是让一个用户和服务器连接,接收预先定义好的数据,然后断开。不过这个程序中有很多错误需要你来修改,作为你的作业来完成:)。 



- ------ 



<++> socket/server.c 

/* SERVER PROGRAM */ 

#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 PORT 1500 /* the port users will be connecting to */ 



#define BACKLOG 5 /* how many pending connections queue will hold */ 



main() 

{  

int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */ 



struct sockaddr_in my_addr; /* our address information */ 

struct sockaddr_in their_addr; /* their address information */ 

int sin_size; 



sockfd = socket(AF_INET, SOCK_STREAM, 0); 

/* remember to error check (-1 on error) */ 



my_addr.sin_family = AF_INET; /* host byte order */ 

my_addr.sin_port = htons(PORT); /* short, network byte order */ 

my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */ 

bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */ 



bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); 



listen(sockfd, BACKLOG) 





while(1) {  /* start out accept() loop */ 

sin_size = sizeof(struct sockaddr_in); 

new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size) 

printf("server: got connection from %s/n", inet_ntoa(their_addr.sin_ad

dr)); 

fork(); /* this is the child process */ 

send(new_fd, "Hello, world!/n", 14, 0) 

close(new_fd); 

exit(0); 



while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */ 







/* END SERVER PROGRAM, REMEMBER TO DO YOUR ERROR CHECKING */ 

<--> 

<++> socket/client.c 

/* CLIENT PROGRAM */ 



#include <stdio.h>  

#include <stdlib.h>  

#include <errno.h>  

#include <string.h>  

#include <netdb.h>  

#include <sys/types.h>  

#include <netinet/in.h>  

#include <sys/socket.h>  



#define PORT 1500 /* the port client will be connecting to */ 



#define MAXDATASIZE 100 /* max number of bytes we can get at once */ 



int main(int argc, char *argv[]) 

{  

int sockfd, numbytes;  

char buf[MAXDATASIZE]; 

struct hostent *he; 

struct sockaddr_in their_addr; /* connector's address information */ 



if (argc != 2) {  

fprintf(stderr,"Usage: client <host name>/n"); 

exit(1); 





he = gethostbyname(argv[1]); /* get the host info */ 

/* did you check for errors? */ 



sockfd = socket(AF_INET, SOCK_STREAM, 0); 



their_addr.sin_family = AF_INET; /* host byte order */ 

their_addr.sin_port = htons(PORT); /* short, network byte order */ 

their_addr.sin_addr = *((struct in_addr *)he->h_addr); 

bzero(&(their_addr.sin_zero), 8); /* zero the rest of the struct */ 



connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr

)); 



numbytes = recv(sockfd, buf, MAXDATASIZE, 0); 



buf[numbytes] = '/0'; 



printf("Received: %s",buf); 



close(sockfd); 



return 0; 





/* END CLIENT...YOU CHECKED FOR ERRORS RIGHT? :) */ 

<--> 



=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==

+==+==+= 



以上是关于SOCKET编程的一些入门的指导,如果还想深入了解的话可以从以下的地方找到资料: 



------- 



Internetworking with TCP/IP, volumes I-III by Douglas E. Comer and Dav

id 

L. Stevens. Published by Prentice Hall. Second edition ISBNs: 0-13-468

505-9,  

0-13-472242-6, 0-13-474222-2. There is a third edition of this set whi

ch 

covers IPv6 and IP over ATM.  



Using C on the UNIX System by David A. Curry. Published by O'Reilly & 



Associates, Inc. ISBN 0-937175-23-4. 



TCP/IP Network Administration by Craig Hunt. Published by O'Reilly & 

Associates, Inc. ISBN 0-937175-82-X.  



TCP/IP Illustrated, volumes 1-3 by W. Richard Stevens and Gary R. Wrig

ht. 

Published by Addison Wesley. ISBNs: 0-201-63346-9, 0-201-63354-X,  

0-201-63495-3.  



UNIX Network Programming by W. Richard Stevens/ Published by Prentice 



Hall. ISBN 0-13-949876-1.  



-------- 

相关的RFC的资料: 

------- 



RFC-768 -- The User Datagram Protocol (UDP) 

(::URL::
ftp://nic.ddn.mil/rfc/rfc768.txt)  



RFC-791 -- The Internet Protocol (IP) 

(::URL::
ftp://nic.ddn.mil/rfc/rfc791.txt)  



RFC-793 -- The Transmission Control Protocol (TCP) 

(::URL::
ftp://nic.ddn.mil/rfc/rfc793.txt)  



RFC-854 -- The Telnet Protocol 

(::URL::
ftp://nic.ddn.mil/rfc/rfc854.txt)  



RFC-951 -- The Bootstrap Protocol (BOOTP) 

(::URL::
ftp://nic.ddn.mil/rfc/rfc951.txt)  



RFC-1350 -- The Trivial File Transfer Protocol (TFTP) 

(::URL::
ftp://nic.ddn.mil/rfc/rfc1350.txt)  

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值