原文链接:http://gstarwd.javaeye.com/blog/539245

关键字: linux-socket c实现

 

socket()


我们使用系统调用 socket() 来获得文件描述符: 
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
第一个参数 domain 设置为 “AF_INET” 。 
第二个参数是套接口的类型: SOCK_STREAM 或 
SOCK_DGRAM
 。第三个参数设置为 0 。 
系统调用 socket() 只返回一个套接口描述符,如果出错,则返回 -1 。  

bind()

一旦你有了一个套接口以后,下一步就是把套接口绑定到本地计算机的某一个端口上。但如果你只想使用 connect() 则无此必要。 
下面是系统调用 bind() 的使用方法: 
#include<sys/types.h>
#include<sys/socket.h>
intbind(int sockfd,struct sockaddr*my_addr,int addrlen);
第一个参数 sockfd 是由 socket() 调用返回的套接口文件描述符。 
第二个参数 my_addr 是指向数据结构 sockaddr 的指针。数据结构 sockaddr 中包括了关于你的地址、端口和 IP 地址的信息。 
第三个参数 addrlen 可以设置成 sizeof(structsockaddr) 。 
下面是一个例子: 

 

C代码 
  1. #include<string.h>  
  2. #include<sys/types.h>  
  3. #include<sys/socket.h>  
  4. #defineMYPORT3490  
  5. main()  
  6. {  
  7. int sockfd;  
  8. struct sockaddr_inmy_addr;  
  9. sockfd=socket(AF_INET,SOCK_STREAM,0);/*do someerror checking!*/  
  10. my_addr.sin_family=AF_INET;/*hostbyteorder*/  
  11. my_addr.sin_port=htons(MYPORT);/*short,network byte order*/  
  12. my_addr.sin_addr.s_addr=inet_addr("132.241.5.10");  
  13. bzero(&(my_addr.sin_zero),8);/*zero the rest of the struct*/  
  14. /*don't forget your error checking for bind():*/  
  15. bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr));  
  16. ...  
 

 

如果出错, bind() 也返回 -1 。 
如果你使用 connect() 系统调用,那么你不必知道你使用的端口号。当你调用 connect() 时,它检查套接口是否已经绑定,如果没有,它将会分配一个空闲的端口。

connect()

系统调用 connect() 的用法如下: 
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd,struct sockaddr* serv_addr,int addrlen);
第一个参数还是套接口文件描述符,它是由系统调用 socket() 返回的。 
第二个参数是 serv_addr 是指向数据结构 sockaddr 的指针,其中包括目的端口和 IP 地址。 
第三个参数可以使用 sizeof(structsockaddr) 而获得。 
下面是一个例子: 
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#defineDEST_IP"132.241.5.10"
#defineDEST_PORT23
main()
{
intsockfd;
structsockaddr_indest_addr;/*will hold the destination addr*/
sockfd=socket(AF_INET,SOCK_STREAM,0);/*do some error checking!*/
dest_addr.sin_family=AF_INET;/*hostbyteorder*/
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*/
/*don'tforgettoerrorchecktheconnect()!*/
connect(sockfd,(structsockaddr*)&dest_addr,sizeof(struct sockaddr));
...
同样,如果出错, connect() 将会返回 -1 。

listen()


如果你希望不连接到远程的主机,也就是说你希望等待一个进入的连接请求,然后再处理它们。这样,你通过首先调用 listen() ,然后再调用 accept() 来实现。 
系统调用 listen() 的形式如下: 
intl isten(int sockfd,int backlog);
第一个参数是系统调用 socket() 返回的套接口文件描述符。 
第二个参数是进入队列中允许的连接的个数。进入的连接请求在使用系统调用 accept() 应答之前要在进入队列中等待。这个值是队列中最多可以拥有的请求的个数。大多数系统的缺省设置为 20 。你可以设置为 5 或者 10 。当出错时, listen() 将会返回 -1 值。 
当然,在使用系统调用 listen() 之前,我们需要调用 bind() 绑定到需要的端口,否则系统内核将会让我们监听一个随机的端口。所以,如果你希望监听一个端口,下面是应该使用的系统调用的顺序: 
socket();
bind();
listen();
/*accept()goeshere*/ 

accept()


系统调用 accept() 比较起来有点复杂。在远程的主机可能试图使用 connect() 连接你使用 
listen()
 正在监听的端口。但此连接将会在队列中等待,直到使用 accept() 处理它。调用 accept()
之后,将会返回一个全新的套接口文件描述符来处理这个单个的连接。这样,对于同一个连接 
来说,你就有了两个文件描述符。原先的一个文件描述符正在监听你指定的端口,新的文件描 
述符可以用来调用 send() 和 recv() 。 
调用的例子如下: 
#include<sys/socket.h>
intaccept(intsockfd,void*addr,int*addrlen);
第一个参数是正在监听端口的套接口文件描述符。第二个参数 addr 是指向本地的数据结构 
sockaddr_in
 的指针。调用 connect() 中的信息将存储在这里。通过它你可以了解哪个主机在哪个 
端口呼叫你。第三个参数同样可以使用 sizeof(structsockaddr_in) 来获得。 
如果出错, accept() 也将返回 -1 。下面是一个简单的例子: 

 

C代码 
  1. #include<string.h>  
  2. #include<sys/types.h>  
  3. #include<sys/socket.h>  
  4. #defineMYPORT3490/*theportuserswillbeconnectingto*/  
  5. #defineBACKLOG10/*howmanypendingconnectionsqueuewillhold*/  
  6. main()  
  7. {  
  8. intsockfd,new_fd;/*listenonsock_fd,newconnectiononnew_fd*/  
  9. structsockaddr_inmy_addr;/*myaddressinformation*/  
  10. structsockaddr_intheir_addr;/*connector'saddressinformation*/  
  11. intsin_size;  
  12. sockfd=socket(AF_INET,SOCK_STREAM,0);/*dosomeerrorchecking!*/  
  13. my_addr.sin_family=AF_INET;/*hostbyteorder*/  
  14. my_addr.sin_port=htons(MYPORT);/*short,networkbyteorder*/  
  15. my_addr.sin_addr.s_addr=INADDR_ANY;/*auto-fillwithmyIP*/  
  16. bzero(&(my_addr.sin_zero),8);/*zerotherestofthestruct*/  
  17. /*don'tforgetyourerrorcheckingforthesecalls:*/  
  18. bind(sockfd,(structsockaddr*)&my_addr,sizeof(structsockaddr));  
  19. listen(sockfd,BACKLOG);  
  20. sin_size=sizeof(structsockaddr_in);  
  21. new_fd=accept(sockfd,&their_addr,&sin_size);  
  22. ...  
 

 


下面,我们将可以使用新创建的套接口文件描述符 new_fd 来调用 send() 和 recv() 。

send() 和 recv()


系统调用 send() 的用法如下: 
int send(int sockfd,const void* msg,int len,int flags);
第一个参数是你希望给发送数据的套接口文件描述符。它可以是你通过 socket() 系统调用返回的,也可以是通过 accept() 系统调用得到的。 
第二个参数是指向你希望发送的数据的指针。 
第三个参数是数据的字节长度。第四个参数标志设置为 0 。 
下面是一个简单的例子: 
char*msg="Beejwashere!";
intlen,bytes_sent;
..
len=strlen(msg);
bytes_sent=send(sockfd,msg,len,0);
...
系统调用 send() 返回实际发送的字节数,这可能比你实际想要发送的字节数少。如果返回的字节数比要发送的字节数少,你在以后必须发送剩下的数据。当 send() 出错时,将返回 -1 。 
系统调用 recv() 的使用方法和 send() 类似: 
int recv(int sockfd,void* buf,int len,unsigned int flags);
第一个参数是要读取的套接口文件描述符。 
第二个参数是保存读入信息的地址。 
第三个参数是缓冲区的最大长度。第四个参数设置为 0 。 
系统调用 recv() 返回实际读取到缓冲区的字节数,如果出错则返回 -1 。 
这样使用上面的系统调用,你可以通过数据流套接口来发送和接受信息。  

sendto() 和 recvfrom()


因为数据报套接口并不连接到远程的主机上,所以在发送数据包之前,我们必须首先给出目的地址,请看: 
int sendto(int sockfd,const void* msg,int len,unsigned int flags,
conststruct sockaddr*to,inttolen);
除了两个参数以外,其他的参数和系统调用 send() 时相同。 
参数 to 是指向包含目的 IP 地址和端口号的数据结构 sockaddr 的指针。 
参数 tolen 可以设置为 sizeof(structsockaddr) 。 
系统调用 sendto() 返回实际发送的字节数,如果出错则返回 -1 。 
系统调用 recvfrom() 的使用方法也和 recv() 的十分近似: 
int recvfrom(int sockfd,void* buf,int len,unsigned int flags
struct sockaddr* from,int* fromlen);
参数 from 是指向本地计算机中包含源 IP 地址和端口号的数据结构 sockaddr 的指针。 
参数 fromlen 设置为 sizeof(struct sockaddr) 。 
系统调用 recvfrom() 返回接收到的字节数,如果出错则返回 -1 。

close() 和 shutdown()


你可以使用 close() 调用关闭连接的套接口文件描述符: 
close(sockfd);
这样就不能再对此套接口做任何的读写操作了。 
使用系统调用 shutdown() ,可有更多的控制权。它允许你在某一个方向切断通信,或者切断双方的通信: 
int shutdown(int sockfd,int how);
第一个参数是你希望切断通信的套接口文件描述符。第二个参数 how 值如下: 
0—Furtherreceivesaredisallowed
1—Furthersendsaredisallowed
2—Furthersendsandreceivesaredisallowed(likeclose())
shutdown()
 如果成功则返回 0 ,如果失败则返回 -1 。

getpeername()


这个系统的调用十分简单。它将告诉你是谁在连接的另一端: 
#include<sys/socket.h>
int getpeername(int sockfd,struct sockaddr* addr,int* addrlen);
第一个参数是连接的数据流套接口文件描述符。 
第二个参数是指向包含另一端的信息的数据结构 sockaddr 的指针。 
第三个参数可以设置为 sizeof(structsockaddr) 。 
如果出错,系统调用将返回 -1 。 
一旦你获得了它们的地址,你可以使用 inet_ntoa() 或者 gethostbyaddr() 来得到更多的信息。

gethostname()


系统调用 gethostname() 比系统调用 getpeername() 还简单。它返回程序正在运行的计算机的名字。系统调用 gethostbyname() 可以使用这个名字来决定你的机器的 IP 地址。 
下面是一个例子: 
#include<unistd.h>
int gethostname(char*hostname,size_tsize);
如果成功, gethostname 将返回 0 。如果失败,它将返回 -1 。  

SOCKET C 程序代码

Makefile 文件

s: app_service.c

       gcc -o s app_service.c

c: app_client.c

       gcc -o c app_client.c

 

app_client.c 文件

 

C代码 
  1. //客户端程序代码如下:  
  2.   
  3. #include<stdio.h>  
  4.   
  5. #include <stdlib.h>  
  6.   
  7. #include <errno.h>  
  8.   
  9. #include <string.h>  
  10.   
  11. #include <netdb.h>  
  12.   
  13. #include <sys/types.h>  
  14.   
  15. #include <netinet/in.h>  
  16.   
  17. #include <sys/socket.h>  
  18.   
  19. #define SERVPORT 3333  
  20.   
  21. #define MAXDATASIZE 100              // 每次最大数据传输量  
  22.   
  23.    
  24.   
  25. main(int argc, char *argv[])  
  26.   
  27. {  
  28.   
  29.        int sockfd, recvbytes;  
  30.   
  31.        char buf[MAXDATASIZE];  
  32.   
  33.        struct hostent *host;  
  34.   
  35.        struct sockaddr_in serv_addr;  
  36.   
  37.    
  38.   
  39.        if (argc < 2) {  
  40.   
  41.               fprintf(stderr,"Please enter the server's hostname!/n");  
  42.   
  43.               exit(1);  
  44.   
  45.        }  
  46.   
  47.        if ((host = gethostbyname(argv[1])) == NULL) {  
  48.   
  49.               herror("gethostbyname出错!");  
  50.   
  51.               exit(1);  
  52.   
  53.        }  
  54.   
  55.        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){  
  56.   
  57.               perror("socket创建出错!");  
  58.   
  59.               exit(1);  
  60.   
  61.        }  
  62.   
  63.        serv_addr.sin_family = AF_INET;  
  64.   
  65.        serv_addr.sin_port = htons(SERVPORT);  
  66.   
  67.        serv_addr.sin_addr = *((struct in_addr *)host->h_addr);  
  68.   
  69.        bzero(&(serv_addr.sin_zero), 8);  
  70.   
  71.        if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1) {  
  72.   
  73.               perror("connect出错!");  
  74.   
  75.               exit(1);  
  76.   
  77.        }  
  78.   
  79.        if ((recvbytes = recv(sockfd, buf, MAXDATASIZE, 0)) ==-1) {  
  80.   
  81.               perror("recv出错!");  
  82.   
  83.               exit(1);  
  84.   
  85.        }  
  86.   
  87.        buf[recvbytes] = '/0';  
  88.   
  89.        printf("Received: %s",buf);  
  90.   
  91.        close(sockfd);  
  92.   
  93. }  
  94.   
  95.    
  96.   
  97. /* 
  98.  
  99.     客户端程序首先通过服务器域名获得服务器的IP地址,然后创建一个socket,调用connect函数与服务器建立连接,连接成功之后接收从服务器发送过来的数据,最后关闭socket。 
  100.  
  101.   函数gethostbyname()是完成域名转换的。由于IP地址难以记忆和读写,所以为了方便,人们常常用域名来表示主机,这就需要进行域名和IP地址的转换。函数原型为: 
  102.  
  103.   struct hostent *gethostbyname(const char *name); 
  104.  
  105.   函数返回为hosten的结构类型,它的定义如下: 
  106.  
  107.   struct hostent { 
  108.  
  109.        char *h_name; // 主机的官方域名 
  110.  
  111.        char **h_aliases; // 一个以NULL结尾的主机别名数组 
  112.  
  113.        int h_addrtype; // 返回的地址类型,在Internet环境下为AF-INET 
  114.  
  115.        int h_length; // 地址的字节长度 
  116.  
  117.        char **h_addr_list; // 一个以0结尾的数组,包含该主机的所有地址 
  118.  
  119.     }; 
  120.  
  121.   #define h_addr h_addr_list[0] //在h-addr-list中的第一个地址 
  122.  
  123.   当 gethostname()调用成功时,返回指向struct hosten的指针,当调用失败时返回-1。当调用gethostbyname时,你不能使用perror()函数来输出错误信息, 
  124.  
  125. 应该使用herror()函数来输出。 
  126.  
  127.   
  128.  
  129.   无连接的客户/服务器程序的在原理上和连接的客户/服务器是一样的,两者的区别在于无连接的客户/服务器中的客户一般不需要建立连接,而且在发送接收 
  130.  
  131. 数据时,需要指定远端机的地址。 
  132.  
  133.   
  134.  
  135. */  
 

 

app_service.c 文件

 

C代码 
  1. /* 
  2.  
  3. 面向连接的Socket实例 
  4.  
  5.   代码实例中的服务器通过socket连接向客户端发送字符串"Hello, you are connected!"。只要在服务器上运行该服务器软件,在客户端运行客户软件,客户端就会收到该字符串。 
  6.  
  7.   该服务器软件代码如下: 
  8.  
  9. */  
  10.   
  11.                                                                                                                                                 
  12.   
  13. #include <stdio.h>  
  14.   
  15. #include <stdlib.h>  
  16.   
  17. #include <errno.h>  
  18.   
  19. #include <string.h>  
  20.   
  21. #include <sys/types.h>  
  22.   
  23. #include <netinet/in.h>  
  24.   
  25. #include <sys/socket.h>  
  26.   
  27. #include <sys/wait.h>  
  28.   
  29.    
  30.   
  31. #define SERVPORT 3333     // 服务器监听端口号  
  32.   
  33. #define BACKLOG 10 // 最大同时连接请求数  
  34.   
  35.    
  36.   
  37. main()  
  38.   
  39. {  
  40.   
  41.        int sockfd, client_fd;            // sock_fd:监听socket;client_fd:数据传输socket  
  42.   
  43.        struct sockaddr_in my_addr;        // 本机地址信息  
  44.   
  45.        struct sockaddr_in remote_addr; // 客户端地址信息  
  46.   
  47.        int sin_size;  
  48.   
  49.    
  50.   
  51.        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {  
  52.   
  53.               perror("socket创建出错!"); exit(1);  
  54.   
  55.        }  
  56.   
  57.        my_addr.sin_family = AF_INET;  
  58.   
  59.        my_addr.sin_port = htons(SERVPORT);  
  60.   
  61.        my_addr.sin_addr.s_addr = INADDR_ANY;  
  62.   
  63.        bzero(&(my_addr.sin_zero), 8);  
  64.   
  65.        if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {  
  66.   
  67.               perror("bind出错!");  
  68.   
  69.               exit(1);  
  70.   
  71.        }  
  72.   
  73.        if (listen(sockfd, BACKLOG) == -1) {  
  74.   
  75.               perror("listen出错!");  
  76.   
  77.               exit(1);  
  78.   
  79.        }  
  80.   
  81.        while(1) {  
  82.   
  83.               sin_size = sizeof(struct sockaddr_in);  
  84.   
  85.               if ((client_fd = accept(sockfd, (void *)&remote_addr, &sin_size)) == -1) {  
  86.   
  87.                      perror("accept出错");  
  88.   
  89.                      continue;  
  90.   
  91.               }  
  92.   
  93.               printf("received a connection from %s/n", inet_ntoa(remote_addr.sin_addr));  
  94.   
  95.               if (!fork()) { /* 子进程代码段 */  
  96.   
  97.                      if (send(client_fd, "Hello, you are connected!/n", 26, 0) == -1) {  
  98.   
  99.                             perror("send出错!");  
  100.   
  101.                             close(client_fd);  
  102.   
  103.                             exit(0);  
  104.   
  105.                      }  
  106.   
  107.               }  
  108.   
  109.               close(client_fd);  
  110.   
  111.        }  
  112.   
  113. }  
  114.   
  115. /* 
  116.  
  117.  服务器的工作流程是这样的:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,然后调用 listen在相应的socket上监听,当accpet接收到一个连接服务请求时,将生成一个新的socket。服务器显示该客户机的IP地址,并通过新的socket向客户端发送字符串"Hello,you are connected!"。最后关闭该socket。 
  118.  
  119.   代码实例中的fork()函数生成一个子进程来处理数据传输部分,fork()语句对于子进程返回的值为0。所以包含fork函数的if语句是子进程代码部分,它与if语句后面的父进程代码部分是并发执行的。 
  120.  
  121. */  

Logo

更多推荐