Chinaunix首页 | 论坛 | 博客
  • 博客访问: 52510
  • 博文数量: 25
  • 博客积分: 166
  • 博客等级: 入伍新兵
  • 技术积分: 177
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-28 08:57
文章分类

全部博文(25)

文章存档

2015年(2)

2013年(1)

2012年(18)

2011年(4)

我的朋友

分类:

2012-04-13 18:13:11

Linux系统是通过提供套接字(socket)来进行网络编程的.网络程序通过socket和其它几个函数的调用,会返回一个 通讯的文件描述符,我们可以将这个描述符看成普通的文件的描述符来操作,这就是linux的设备无关性的好处.
我们可以通过向描述符读写操作实现网络之间的数据交流.
(一)socket

功能描述:

为通信建立一个终点,并返回新建套接字的描述词。当out-of-band数据到达,或者基于流的连接无意被中断而引起的SIGPIPE信号时,一个fcntl的F_SETOWN操作可用来指定接收SIGURG信号的进程或进程组。

用法:

#include
#include

int socket(int domain, int type, int protocol);

参数:  
domain通信域,选择用于通信的协议族,当前支持值有

名称                            用途                                   手册页
PF_UNIX, PF_LOCAL    本地通信                            unix(7)
PF_INET                       IPv4 因特网协议                 ip(7)
PF_INET6                     IPv6 因特网协议
PF_IPX                         IPX - Novell协议
PF_NETLINK               内核用户接口设备                netlink(7)
PF_X25                         ITU-T X.25 / ISO-8208 协议 x25(7)
PF_AX25                      业余无线电 AX.25 协议
PF_ATMPVC               原始的ATM PVCs访问
PF_APPLETALK            Appletalk                              ddp(7)
PF_PACKET                  低层封包接口                       packet(7)

type套接字类型,当前定义值有

SOCK_STREAM:提供有序的,可靠的,两方的,基于连接的字节流,同时支持out-of-band数据传输机制。这种类型的套接字是全双工的,类似于管道,同时通信前必须建立与另一方的连接。
SOCK_DGRAM:支持固定最大长度的,无连接的,不可靠的数据报文。
SOCK_SEQPACKET:为固定最大长度的数据报提供有序的,可靠的,两方的,基于连接数据传输路径。消费者被要求一次读取整个封包。
SOCK_RAW:提供原始的网络协议访问。
SOCK_RDM:提供可靠,但不会保证有序的数据报层。


protocol通信的协议。通常,在给定的协议族内某种特定的套接字类型只支持一种协议,这种情况下可以将protocol指定为0。如果存在多种协议的情况下,必须指定protocol的值。


返回说明:  
成功执行时,返回新套接字的文件描述词。失败返回-1,errno被设为以下的某个值  
EACCES:权能不允许建立某种类型和/或协议的套接字
EAFNOSUPPORT:指定的地址族不被支持
EINVAL:协议不可识别
EMFILE:进程文件表溢出
ENFILE:已达到系统限制的打开总文件数
ENOBUFS or ENOMEM:资源不足
EPROTONOSUPPORT:通信域不支持指定的协议


(二)bind
int bind(int sockfd, struct sockaddr *my_addr, int addrlen)

sockfd:是由socket调用返回的文件描述符.

addrlen:是sockaddr结构的长度.

my_addr:是一个指向sockaddr的指针. 在中有 sockaddr的定义

struct sockaddr {
unisgned short as_family;
char sa_data[14];
};

不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构(struct sockaddr_in) 来代替.在中有sockaddr_in的定义
struct sockaddr_in {
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
我们主要使用Internet所以
sin_family一般为AF_INET,
sin_addr设置为INADDR_ANY表示可以和任何的主机通信,
sin_port是我们要监听的端口号.sin_zero[8]是用来填充的.
bind将本地的端口同socket返回的文件描述符捆绑在一起.成功是返回0,失败的情况和socket一样

(三)listen
int listen(int sockfd,int backlog)

sockfd:是bind后的文件描述符.

backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度.
listen函数将bind的文件描述符变为监听套接字.返回的情况和bind一样.

(四)accept
int accept(int sockfd, struct sockaddr *addr,int *addrlen)

sockfd:是listen后的文件描述符.

addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了. bind,listen和accept是服务器端用的函数,
accept调用时,服务器端的程序会一直阻塞到有一个 客户程序发出了连接. accept成功时返回最后的服务器端的文件描述符,
这个时候服务器端可以向该描述符写信息了. 失败时返回-1

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

sockfd:socket返回的文件描述符.

serv_addr:储存了服务器端的连接信息.其中sin_add是服务端的地址

addrlen:serv_addr的长度

connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符 失败时返回-1.

(六)实例

点击(此处)折叠或打开

  1. /******* 服务器程序 (server.c) ************/
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <errno.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. #include <netdb.h>
  8. #include <sys/socket.h>
  9. #include <netinet/in.h>
  10. #include <sys/types.h>
  11. #include <arpa/inet.h>

  12. int main(int argc, char *argv[])
  13. {
  14.     int sockfd, new_fd;
  15.     struct sockaddr_in server_addr;
  16.     struct sockaddr_in client_addr;
  17.     socklen_t sin_size;
  18.     unsigned short portnumber;
  19.     char hello[] = "Hello! Are You Fine?\n";

  20.     if (argc != 2) {
  21.     fprintf(stderr, "Usage:%s portnumber\a\n", argv[0]);
  22.     exit(1);
  23.     }

  24.     if ((portnumber = (unsigned short)strtol(argv[1], NULL, 10)) < 0) {
  25.     fprintf(stderr, "Usage:%s portnumber\a\n", argv[0]);
  26.     exit(1);
  27.     }

  28. /* 服务器端开始建立socket描述符 */
  29.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  30.     fprintf(stderr, "Socket error:%s\n\a", strerror(errno));
  31.     exit(1);
  32.     }

  33. /* 服务器端填充 sockaddr结构 */
  34.     bzero(&server_addr, sizeof(struct sockaddr_in));
  35.     server_addr.sin_family = AF_INET;
  36.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  37.     server_addr.sin_port = htons(portnumber);

  38. /* 捆绑sockfd描述符 */
  39.     if (bind
  40.     (sockfd, (struct sockaddr *) (&server_addr),
  41.      sizeof(struct sockaddr)) == -1) {
  42.     fprintf(stderr, "Bind error:%s\n\a", strerror(errno));
  43.     exit(1);
  44.     }

  45. /* 监听sockfd描述符 */
  46.     if (listen(sockfd, 5) == -1) {
  47.     fprintf(stderr, "Listen error:%s\n\a", strerror(errno));
  48.     exit(1);
  49.     }

  50.     while (1) {
  51. /* 服务器阻塞,直到客户程序建立连接 */
  52.     sin_size = sizeof(struct sockaddr_in);
  53.     if ((new_fd =

  54.         /*int accept(int s, struct sockaddr *addr, socklen_t *addrlen);*/
  55.      accept(sockfd, (struct sockaddr *)&client_addr,
  56.          &sin_size)) == -1) {
  57.      fprintf(stderr, "Accept error:%s\n\a", strerror(errno));
  58.      exit(1);
  59.     }

  60.     fprintf(stderr, "Server get connection from %s\n",
  61.         inet_ntoa(client_addr.sin_addr));
  62.     if (write(new_fd, hello, strlen(hello)) == -1) {
  63.      fprintf(stderr, "Write Error:%s\n", strerror(errno));
  64.      exit(1);
  65.     }
  66. /* 这个通讯已经结束 */
  67.     close(new_fd);
  68. /* 循环下一个 */
  69.     }
  70.     close(sockfd);
  71.     exit(0);

  72. }

点击(此处)折叠或打开

  1. /******* 客户端程序 client.c ************/
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <errno.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. #include <netdb.h>
  8. #include <sys/socket.h>
  9. #include <netinet/in.h>
  10. #include <sys/types.h>
  11. #include <arpa/inet.h>

  12. int main(int argc, char *argv[])
  13. {
  14.     int sockfd;
  15.     char buffer[1024];
  16.     struct sockaddr_in server_addr;
  17.     struct hostent *host;
  18.     int portnumber, nbytes;

  19.     if (argc != 3) {
  20.     fprintf(stderr, "Usage:%s hostname portnumber\a\n", argv[0]);
  21.     exit(1);
  22.     }

  23.     if ((host = gethostbyname(argv[1])) == NULL) {
  24.     fprintf(stderr, "Gethostname error\n");
  25.     exit(1);
  26.     }

  27.     if ((portnumber = atoi(argv[2])) < 0) {
  28.     fprintf(stderr, "Usage:%s hostname portnumber\a\n", argv[0]);
  29.     exit(1);
  30.     }

  31. /* 客户程序开始建立 sockfd描述符 */
  32.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  33.     fprintf(stderr, "Socket Error:%s\a\n", strerror(errno));
  34.     exit(1);
  35.     }

  36. /* 客户程序填充服务端的资料 */
  37.     bzero(&server_addr, sizeof(server_addr));
  38.     server_addr.sin_family = AF_INET;
  39.     server_addr.sin_port = htons(portnumber);
  40.     server_addr.sin_addr = *((struct in_addr *) host->h_addr);

  41. /* 客户程序发起连接请求 */
  42.     if (connect
  43.     (sockfd, (struct sockaddr *) (&server_addr),
  44.      sizeof(struct sockaddr)) == -1) {
  45.     fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
  46.     exit(1);
  47.     }

  48. /* 连接成功了 */
  49.     if ((nbytes = read(sockfd, buffer, 1024)) == -1) {
  50.     fprintf(stderr, "Read Error:%s\n", strerror(errno));
  51.     exit(1);
  52.     }
  53.     buffer[nbytes] = '\0';
  54.     printf("I have received:%s\n", buffer);
  55. /* 结束通讯 */
  56.     close(sockfd);
  57.     exit(0);
  58. }
这里我们使用GNU 的make实用程序来编译. 关于make的详细说明见 Make 使用介绍

点击(此处)折叠或打开

  1. ##################Makefile##############
  2. CC = gcc
  3. CFLAGS = -Wall -g -D_DEBUG_

  4. TARGETS = server client

  5. .PHONY: clean all

  6. all : $(TARGETS)

  7. clean:
  8. $(RM) -f a.out *.o *~ $(TARGETS)
运行make后会产生两个程序server(服务器端)和client(客户端) 先运行

./server portnumber&  

(portnumber随便取一个大于1024且不在/etc/services中出现的号码 就用8000好了),

然后运行

./client localhost 8000

看看有什么结果. (你也可以用telnet和netstat试一试.) 

上面是一个最简单的网络程序,不过是不是也有点烦.上面有许多函数我们还没有解释. 我会在下一章进行的详细的说明.

(七) 总结
总的来说网络程序是由两个部分组成的--客户端和服务器端.它们的建立步骤一般是:

服务器端
socket-->bind-->listen-->accept -->r/w-->close

客户端
socket-->connect-->r/w-->close
阅读(186) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~