LINUX 网络应用程序设计

来源:互联网 发布:磁链下载软件 编辑:程序博客网 时间:2024/04/30 03:39

Linux中的网络编程通过Socket(套接字)接口实现,Socket是一种文件描述符。


类型:
套接字socket有三种类型:
1)流式套接字(SOCK_STREAM),流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP保证了数据传输的正确性和顺序性。

2)数据报套接字(SOCK_DGRAM),数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错,它使用数据报协议UDP。

3)原始套接字,原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议的测试等。


地址结构:

struct sockaddr
{
     u_short  sa_family;
     char  sa_data[14];
}
sa_family:地址族,采用“AF_xxx”的形式,如:AF_INET。
sa_data:14字节的特定协议地址。

struct sockaddr_in
{
      short int sin_family; /* Internet地址族 */
      unsigned short int sin_port; /* 端口号 */
      struct in_addr sin_addr; /* IP地址 */
      unsigned char sin_zero[8]; /* 填0 */
}

struct in_addr
{
      unsigned long s_addr;
}
s_addr: 32位的地址。


地址转换:

IP地址通常由数字加点(192.168.0.1)的形式表示,而在struct in_addr中使用的是IP地址是由32位的整数表示的,为了转换我们可以使用下面两个函数:

int inet_aton(const char *cp,struct in_addr *inp);

char *inet_ntoa(struct in_addr in);

函数里面 a 代表 ascii n 代表network.第一个函数表示将a.b.c.d形式的IP转换为32位的IP,存储在 inp指针里面。第二个是将32位IP转换为a.b.c.d的格式。


字节序转换:

不同类型的 CPU 对变量的字节存储顺序可能不同:有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后,而网络传输的数据顺序是一定要统一的。所以当内部字节存储顺序和网络字节顺序不同时,就一定要进行转换。

网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。

htons:
把unsigned short类型从主机序转换到网络序
htonl:
把unsigned long类型从主机序转换到网络序
ntohs:
把unsigned short类型从网络序转换到主机序
ntohl:
把unsigned long类型从网络序转换到主机序


IP与主机名:

在网络上标识一台机器可以用IP,也可以使用主机名。
struct hostent *gethostbyname(const char *hostname)
struct hostent
{
          char *h_name;    /* 主机的正式名称 */
          char *h_aliases; /* 主机的别名 */
          int h_addrtype;    /* 主机的地址类型 AF_INET*/
          int h_length;         /* 主机的地址长度 */
          char **h_addr_list; /* 主机的IP地址列表 */
}
#define h_addr h_addr_list[0] /* 主机的第一个IP地址*/


函数:

进行Socket编程的常用函数有:
• socket
创建一个socket。
• bind
用于绑定IP地址和端口号到socket。
• connect
该函数用于绑定之后的client端,与服务器建立连接。

• listen
设置能处理的最大连接要求,Listen()并未开始接收连线,只是设置socket为listen模式。
• accept
用来接受socket连接。
• send
发送数据
• recv
接收数据


基于TCP-服务器:

1. 创建一个socket,用函数socket()
2. 绑定IP地址、端口等信息到socket上,用函数bind()
3. 设置允许的最大连接数,用函数listen()
4. 接收客户端上来的连接,用函数accept()
5. 收发数据,用函数send()和recv(),或者read()和write()
6. 关闭网络连接

基于TCP-客户端:

1. 创建一个socket,用函数socket()
2. 设置要连接的对方的IP地址和端口等属性
3. 连接服务器,用函数connect()
4. 收发数据,用函数send()和recv(),或者read()和write()
5. 关闭网络连接

tcp_client.c

#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #define portnumber 3333int main(int argc, char *argv[]) { int sockfd; char buffer[1024]; struct sockaddr_in server_addr; struct hostent *host; if(argc!=2) { fprintf(stderr,"Usage:%s hostname \a\n",argv[0]); exit(1); }     /* 使用hostname查询host 名字 */if((host=gethostbyname(argv[1]))==NULL) { fprintf(stderr,"Gethostname error\n"); exit(1); } /* 客户程序开始建立 sockfd描述符 */ if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:Internet;SOCK_STREAM:TCP{ fprintf(stderr,"Socket Error:%s\a\n",strerror(errno)); exit(1); } /* 客户程序填充服务端的资料 */ bzero(&server_addr,sizeof(server_addr)); // 初始化,置0server_addr.sin_family=AF_INET;          // IPV4server_addr.sin_port=htons(portnumber);  // (将本机器上的short数据转化为网络上的short数据)端口号server_addr.sin_addr=*((struct in_addr *)host->h_addr); // IP地址/* 客户程序发起连接请求 */ if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1) { fprintf(stderr,"Connect Error:%s\a\n",strerror(errno)); exit(1); } /* 连接成功了 */ printf("Please input char:\n");/* 发送数据 */fgets(buffer,1024,stdin); write(sockfd,buffer,strlen(buffer)); /* 结束通讯 */ close(sockfd); exit(0); } 
tcp_server.c

#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #define portnumber 3333int main(int argc, char *argv[]) { int sockfd,new_fd; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int sin_size; int nbytes;char buffer[1024];/* 服务器端开始建立sockfd描述符 */ if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:IPV4;SOCK_STREAM:TCP{ fprintf(stderr,"Socket error:%s\n\a",strerror(errno)); exit(1); } /* 服务器端填充 sockaddr结构 */ bzero(&server_addr,sizeof(struct sockaddr_in)); // 初始化,置0server_addr.sin_family=AF_INET;                 // Internetserver_addr.sin_addr.s_addr=htonl(INADDR_ANY);  // (将本机器上的long数据转化为网络上的long数据)服务器程序能运行在任何ip的主机上  //INADDR_ANY 表示主机可以是任意IP地址,即服务器程序可以绑定到所有的IP上//server_addr.sin_addr.s_addr=inet_addr("192.168.1.1");  //用于绑定到一个固定IP,inet_addr用于把数字加格式的ip转化为整形ipserver_addr.sin_port=htons(portnumber);         // (将本机器上的short数据转化为网络上的short数据)端口号/* 捆绑sockfd描述符到IP地址 */ if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1) { fprintf(stderr,"Bind error:%s\n\a",strerror(errno)); exit(1); } /* 设置允许连接的最大客户端数 */ 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=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((nbytes=read(new_fd,buffer,1024))==-1) { fprintf(stderr,"Read Error:%s\n",strerror(errno)); exit(1); } buffer[nbytes]='\0';printf("Server received %s\n",buffer);/* 这个通讯已经结束 */ close(new_fd); /* 循环下一个 */ } /* 结束通讯 */ close(sockfd); exit(0); } 

基于UDP-服务器

1. 创建一个socket,用函数socket()
2. 绑定IP地址、端口等信息到socket上,用函数bind()
3. 循环接收数据,用函数recvfrom()
4. 关闭网络连接基于

UDP-客户端

1. 创建一个socket,用函数socket()
2. 绑定IP地址、端口等信息到socket上,
用函数bind()
3. 设置对方的IP地址和端口等属性
4. 发送数据,用函数sendto()
5. 关闭网络连接

udp_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>#define SERVER_PORT 8888 #define MAX_BUF_SIZE 1024 void udpc_requ(int sockfd,const struct sockaddr_in *addr,int len) { char buffer[MAX_BUF_SIZE]; int n; while(1) { /* 从键盘读入,写到服务端 */ printf("Please input char:\n");fgets(buffer,MAX_BUF_SIZE,stdin); sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr *)addr,len); bzero(buffer,MAX_BUF_SIZE); } } int main(int argc,char **argv) { int sockfd; struct sockaddr_in addr; if(argc!=2) { fprintf(stderr,"Usage:%s server_ip\n",argv[0]); exit(1); }/* 建立 sockfd描述符 */ sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0) { fprintf(stderr,"Socket Error:%s\n",strerror(errno)); exit(1); } /* 填充服务端的资料 */ bzero(&addr,sizeof(struct sockaddr_in)); addr.sin_family=AF_INET; addr.sin_port=htons(SERVER_PORT);if(inet_aton(argv[1],&addr.sin_addr)<0)  /*inet_aton函数用于把字符串型的IP地址转化成网络2进制数字*/ { fprintf(stderr,"Ip error:%s\n",strerror(errno)); exit(1); } udpc_requ(sockfd,&addr,sizeof(struct sockaddr_in)); // 进行读写操作close(sockfd); } 
udp_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>#define SERVER_PORT 8888 #define MAX_MSG_SIZE 1024 void udps_respon(int sockfd) { struct sockaddr_in addr; int addrlen,n; char msg[MAX_MSG_SIZE]; while(1) {/* 从网络上读,并写到网络上 */ bzero(msg,sizeof(msg)); // 初始化,清零addrlen = sizeof(struct sockaddr);n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,(struct sockaddr*)&addr,&addrlen); // 从客户端接收消息msg[n]=0;//将收到的字符串尾端添加上字符串结束标志 /* 显示服务端已经收到了信息 */ fprintf(stdout,"Server have received %s",msg); // 显示消息} } int main(void) { int sockfd; struct sockaddr_in addr; /* 服务器端开始建立socket描述符 */ sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0) { fprintf(stderr,"Socket Error:%s\n",strerror(errno)); exit(1); } /* 服务器端填充 sockaddr结构 */ bzero(&addr,sizeof(struct sockaddr_in)); addr.sin_family=AF_INET; addr.sin_addr.s_addr=htonl(INADDR_ANY); addr.sin_port=htons(SERVER_PORT); /* 捆绑sockfd描述符 */ if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in))<0) { fprintf(stderr,"Bind Error:%s\n",strerror(errno)); exit(1); } udps_respon(sockfd); // 进行读写操作close(sockfd); } 

服务器模型:

在网络程序里面,一般来说都是许多客户对应一个服务器,为了处理客户的请求, 对服务端的程序就提出了特殊的要求。目前最常用的服务器模型有:
•循环服务器:服务器在同一个时刻只可以响应一个客户端的请求
•并发服务器:服务器在同一个时刻可以响应多个客户端的请求


UDP循环服务器

UDP循环服务器的实现方法:UDP服务器每次从套接字上读取一个客户端的请求->处理->然后将结果返回给客户机。
socket(...);
bind(...);
while(1)
{
      recvfrom(...);
      process(...);
      sendto(...);
}
因为UDP是非面向连接的,没有一个客户端可以老是占住服务端, 服务器对于每一个客户机的请求总是能够满足。


TCP循环服务器

TCP循环服务器一次只能处理一个客户端的请求。只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求。这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了,因此,TCP服务器一般很少用循环服务器模型的。

TCP并发服务器
并发服务器的思想是每一个客户机的请求并不由服务器直接处
理,而是由服务器创建一个 子进程来处理。算法如下:
socket(...);
bind(...);
listen(...);
while(1) {
     accept(...);
     if(fork(..)==0) {
          process(...);
          close(...);
          exit(...);
      }
      close(...);
}

TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况。但同时也带来了问题:为了响应客户的请求,服务器要创建子进程来处理,而创建子进程是一种非常消耗资源的操作。

多路复用I/O:

阻塞函数在完成其指定的任务以前不允许程序继续向下执行。例如:当服务器运行到accept语句时,而没有客户请求连接,服务器就会停止在accept语句上等待连接请求的到来。这种情况称为阻塞(blocking),而非阻塞操作则可以立即完成。例如,如果你希望服务器仅仅检查是否有客户在等待连接,有就接受连接,否则就继续做其他事情,则可以通过使用select系统调用来实现。除此之外,select还可以同时监视多个套接字 。

int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set
*exceptfds, const struct timeval *timeout)
   Maxfd: 文件描述符的范围,比待检的最大文件描述符大1
   Readfds:被读监控的文件描述符集
   Writefds:被写监控的文件描述符集
   Exceptfds:被异常监控的文件描述符集
   Timeout:定时器

       Timeout取不同的值,该调用有不同的表现:
       Timeout值为0,不管是否有文件满足要求,都立刻返回,无文件满足要求返回0,有文件满足要求返回一个正值。
       Timeout为NULL,select将阻塞进程,直到某个文件满足要求
       Timeout值为正整数,就是等待的最长时间,即select在timeout时间内阻塞进程。

Select调用返回时,返回值有如下情况:
1. 正常情况下返回满足要求的文件描述符个数;
2. 经过了timeout等待后仍无文件满足要求,返回值为0;
3. 如果select被某个信号中断,它将返回-1并设置errno为EINTR。
4. 如果出错,返回-1并设置相应的errno。

系统提供了4个宏对描述符集进行操作:
#include <sys/select.h>
void FD_SET(int fd, fd_set *fdset)
void FD_CLR(int fd, fd_set *fdset)
void FD_ZERO(fd_set *fdset)
void FD_ISSET(int fd, fd_set *fdset)
宏FD_SET将文件描述符fd添加到文件描述符集fdset中;
宏FD_CLR从文件描述符集fdset中清除文件描述符fd;
宏FD_ZERO清空文件描述符集fdset;

在调用select后使用FD_ISSET来检测文件描述符集fdset中的文件fd发生了变化。

client.c

#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <sys/un.h>#include <netdb.h>#include <unistd.h>int main(int argc,char *argv[]){    int connect_fd;int ret;char snd_buf[1024];int i;int port;int len;static struct sockaddr_in srv_addr;//客户端运行需要给出具体的连接地址和端口 if(argc!=3){    printf("Usage: %s server_ip_address port\n",argv[0]);return 1;}//获得输入的端口port=atoi(argv[2]);//创建套节字用于客户端的连接connect_fd=socket(PF_INET,SOCK_STREAM,0);if(connect_fd<0){    perror("cannot create communication socket");return 1;}//填充关于服务器的套节字信息memset(&srv_addr,0,sizeof(srv_addr));srv_addr.sin_family=AF_INET;srv_addr.sin_addr.s_addr=inet_addr(argv[1]);srv_addr.sin_port=htons(port);//连接指定的服务器 ret=connect(connect_fd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));if(ret==-1){    perror("cannot connect to the server");close(connect_fd);return 1;}memset(snd_buf,0,1024);//用户输入信息后,程序将输入的信息通过套接字发送给服务器 //然后调用read函数从服务器中读取发送来的信息 //当输入“@”时,程序退出 while(1){    write(STDOUT_FILENO,"input message:",14);len=read(STDIN_FILENO,snd_buf,1024);if(len>0)write(connect_fd,snd_buf,len);len=read(connect_fd,snd_buf,len);if(len>0)printf("Message form server: %s\n",snd_buf);if(snd_buf[0]=='@')break;}close(connect_fd);return 0;}
server_thread.c

#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <sys/un.h>#include <errno.h>#include <unistd.h>#include <signal.h>#include <sys/wait.h>#include <netdb.h>#include <pthread.h>//线程执行函数负责读写void *thr_fn(void *arg){int size,j;    char recv_buf[1024];int *parg=(int *)arg;int new_fd=*parg;printf("new_fd=%d\n",new_fd);while((size=read(new_fd,recv_buf,1024))>0){if(recv_buf[0]=='@')break;printf("Message from client(%d): %s\n",size,recv_buf);for(j=0;j<size;j++)recv_buf[j]=toupper(recv_buf[j]);write(new_fd,recv_buf,size);}close(new_fd);return 0;}int main(int argc,char *argv[]){    socklen_t clt_addr_len;int listen_fd;int com_fd;int ret;int i;static char recv_buf[1024];int len;int port;pthread_t tid;struct sockaddr_in clt_addr;struct sockaddr_in srv_addr;//服务器端运行时要给出端口信息,该端口为监听端口 if(argc!=2){    printf("Usage:%s port\n",argv[0]);return 1;}//获得输入的端口 port=atoi(argv[1]);//创建套接字用于服务器的监听 listen_fd=socket(PF_INET,SOCK_STREAM,0);if(listen_fd<0){    perror("cannot create listening socket");return 1;}//填充关于服务器的套节字信息memset(&srv_addr,0,sizeof(srv_addr));srv_addr.sin_family=AF_INET;srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);srv_addr.sin_port=htons(port);//将服务器和套节字绑定ret=bind(listen_fd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));if(ret==-1){    perror("cannot bind server socket");close(listen_fd);return 1;}//监听指定端口,连接5个客户端 ret=listen(listen_fd,5);if(ret==-1){    perror("cannot listen the client connect request");close(listen_fd);return 1;}//对每个连接来的客户端创建一个线程,单独与其进行通信 //首先调用read函数读取客户端发送来的信息 //将其转换成大写后发送回客户端 //当输入“@”时,程序退出 while(1){    len=sizeof(clt_addr);com_fd=accept(listen_fd,(struct sockaddr *)&clt_addr,&len);if(com_fd<0){    if(errno==EINTR){    continue;}else{    perror("cannot accept client connect request");close(listen_fd);return 1;}}printf("com_fd=%d\n",com_fd);//打印建立连接的客户端产生的套节字if((pthread_create(&tid,NULL,thr_fn,&com_fd))==-1){    perror("pthread_create error");close(listen_fd);close(com_fd);return 1;}}return 0;}

原创粉丝点击