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.
(六)实例 点击(此处)折叠或打开
- /******* 服务器程序 (server.c) ************/
- #include <stdlib.h>
- #include <stdio.h>
- #include <errno.h>
- #include <string.h>
- #include <unistd.h>
- #include <netdb.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <sys/types.h>
- #include <arpa/inet.h>
- int main(int argc, char *argv[])
- {
- int sockfd, new_fd;
- struct sockaddr_in server_addr;
- struct sockaddr_in client_addr;
- socklen_t sin_size;
- unsigned short portnumber;
- char hello[] = "Hello! Are You Fine?\n";
- if (argc != 2) {
- fprintf(stderr, "Usage:%s portnumber\a\n", argv[0]);
- exit(1);
- }
- if ((portnumber = (unsigned short)strtol(argv[1], NULL, 10)) < 0) {
- fprintf(stderr, "Usage:%s portnumber\a\n", argv[0]);
- exit(1);
- }
- /* 服务器端开始建立socket描述符 */
- if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
- fprintf(stderr, "Socket error:%s\n\a", strerror(errno));
- exit(1);
- }
- /* 服务器端填充 sockaddr结构 */
- bzero(&server_addr, sizeof(struct sockaddr_in));
- server_addr.sin_family = AF_INET;
- server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- server_addr.sin_port = htons(portnumber);
- /* 捆绑sockfd描述符 */
- if (bind
- (sockfd, (struct sockaddr *) (&server_addr),
- sizeof(struct sockaddr)) == -1) {
- fprintf(stderr, "Bind error:%s\n\a", strerror(errno));
- exit(1);
- }
- /* 监听sockfd描述符 */
- if (listen(sockfd, 5) == -1) {
- fprintf(stderr, "Listen error:%s\n\a", strerror(errno));
- exit(1);
- }
- while (1) {
- /* 服务器阻塞,直到客户程序建立连接 */
- sin_size = sizeof(struct sockaddr_in);
- if ((new_fd =
- /*int accept(int s, struct sockaddr *addr, socklen_t *addrlen);*/
- accept(sockfd, (struct sockaddr *)&client_addr,
- &sin_size)) == -1) {
- fprintf(stderr, "Accept error:%s\n\a", strerror(errno));
- exit(1);
- }
- fprintf(stderr, "Server get connection from %s\n",
- inet_ntoa(client_addr.sin_addr));
- if (write(new_fd, hello, strlen(hello)) == -1) {
- fprintf(stderr, "Write Error:%s\n", strerror(errno));
- exit(1);
- }
- /* 这个通讯已经结束 */
- close(new_fd);
- /* 循环下一个 */
- }
- close(sockfd);
- exit(0);
- }
点击(此处)折叠或打开
- /******* 客户端程序 client.c ************/
- #include <stdlib.h>
- #include <stdio.h>
- #include <errno.h>
- #include <string.h>
- #include <unistd.h>
- #include <netdb.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <sys/types.h>
- #include <arpa/inet.h>
- int main(int argc, char *argv[])
- {
- int sockfd;
- char buffer[1024];
- struct sockaddr_in server_addr;
- struct hostent *host;
- int portnumber, nbytes;
- if (argc != 3) {
- fprintf(stderr, "Usage:%s hostname portnumber\a\n", argv[0]);
- exit(1);
- }
- if ((host = gethostbyname(argv[1])) == NULL) {
- fprintf(stderr, "Gethostname error\n");
- exit(1);
- }
- if ((portnumber = atoi(argv[2])) < 0) {
- fprintf(stderr, "Usage:%s hostname portnumber\a\n", argv[0]);
- exit(1);
- }
- /* 客户程序开始建立 sockfd描述符 */
- if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
- fprintf(stderr, "Socket Error:%s\a\n", strerror(errno));
- exit(1);
- }
- /* 客户程序填充服务端的资料 */
- bzero(&server_addr, sizeof(server_addr));
- server_addr.sin_family = AF_INET;
- server_addr.sin_port = htons(portnumber);
- server_addr.sin_addr = *((struct in_addr *) host->h_addr);
- /* 客户程序发起连接请求 */
- if (connect
- (sockfd, (struct sockaddr *) (&server_addr),
- sizeof(struct sockaddr)) == -1) {
- fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
- exit(1);
- }
- /* 连接成功了 */
- if ((nbytes = read(sockfd, buffer, 1024)) == -1) {
- fprintf(stderr, "Read Error:%s\n", strerror(errno));
- exit(1);
- }
- buffer[nbytes] = '\0';
- printf("I have received:%s\n", buffer);
- /* 结束通讯 */
- close(sockfd);
- exit(0);
- }
这里我们使用GNU 的make实用程序来编译. 关于make的详细说明见 Make 使用介绍点击(此处)折叠或打开
- ##################Makefile##############
- CC = gcc
- CFLAGS = -Wall -g -D_DEBUG_
- TARGETS = server client
- .PHONY: clean all
- all : $(TARGETS)
- clean:
- $(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) |