一、问题思考
问1.网络通信应用在什么场合?通信的前提是什么?
答1.主要应用在不同主机进程间的互相通信,同一主机的进程也可以使用网络进行通信。通信的前提是如何标识通信进程的唯一,由于不同主机的进程极有可能具有相同的PID,因此,在网络中单单靠PID是无法准确进行标识进程身份的,TCP/IP协议族网络层的IP地址可以唯一的标识连入网络的主机。
问2.socket编程重点是什么?
答2.掌握基于TCP、UDP的S/C架构的编程要点;掌握网络通信方式之间的区别和应用场合。
问3.什么是网络模型?
答3.网络模型主要分为4层,从上至下依次为:应用层、运输层、协议层、链路层。如图所示。
问4.什么是socket套接字?
答4.是一种文件描述符。有三种类型:
①流式套接字(SOCK_STREAM)——过程类似于打电话。提供可靠的、面向连接的通信流,对应使用TCP/IP协议,能够保证数据传输的正确性和顺序性。
②数据报套接字(SOCK_DGRAM)——过程类似于手机之间发短信。无连接服务、数据的传输是独立的不需要经过对方的响应,可靠性无保证。对应使用UDP/IP协议。
③原始套接字(SOCK_RAW)——该套接字直接基于IP地址。
二、TCP、UDP通信编程模型
主要相关操作函数
1. 创建套接字
#include<sys/types.h> #include<sys/socket.h> int socket(int protofamil,int type,int protocol);
protofamil 设置协议族即设置socket的地址类型,定义在/usr/include/bits/socket.h 内,有:AF_INET(IPV4——32位IP、16位的端口号)、AF_INET6(IPV6)、AF_LOCAL(Unix域socket)、AF_ROUTE等等; type 常用的有SOCK_STREAM、SOCK_DGRAM和SOCK_RAW; protocol 用来指定socket所使用的传输协议编号,通常此参考不用管它,设为0即可。
参数含义
返回值:成功——socket描述符;失败——(-1)
2.绑定地址
#include<sys/types.h> #include<sys/socket.h> int bind(int sockfd,struct sockaddr * my_addr,int addrlen);
sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。 my_addr:指向sockfd要绑定的协议地址。IPv4对应的协议地址结构为: struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ }; addrlen:对应的是地址的长度,使用sizeof来计算。
参数含义
返回值:成功——(0),失败——(-1)
3.监听
#include<sys/socket.h> int listen(int sockfd,int backlog);
参数backlog是能处理的最大连接数目,如果连接数目达此上限则client端将出错。
返回值:成功——(0),失败——(-1)
4.建立连接
#include<sys/types.h> #include<sys/socket.h> int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
参数:sockaddr 服务器的socket地址,addrlen为socket地址的长度。
返回值:成功——(0),失败——(-1)
5.接受请求
#include<sys/types.h> #include<sys/socket.h> int accept(int sockfd,struct sockaddr * addr,int * addrlen);
参数:sockfd ——用来监听客户的一个端口号;addr——客户机的地址数据;addrlen即前面这个地址数据的大小
返回值:成功——已建立连接的新的套接字描述符,可用于read/write函数;失败——(-1)
6.读写操作
第①组:用于面向连接的TCP编程中
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);
返回值:成功——字节数;失败——(-1)
第②组:用于面向连接的TCP编程中
#include<sys/types.h> #include<sys/socket.h> int recv(int sockfd,void *buf,int len,unsigned int flags); int send(int sockfd,const void * msg,int len,unsigned int falgs);
参数:buf/msg分别指向接收缓冲区和发送的信息,len是接收或发送的最大长度,flags通常设置为0。
返回值:成功——字节数;失败——(-1)
第③组:可用于面向连接和无连接的套接字
#include<sys/types.h> #include<sys/socket.h> int recvfrom(int sockfd,void *buf,int len,unsigned int flags ,struct sockaddr *from ,int *fromlen); int sendto ( int sockfd, const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen ) ; int recvmsg(int sockfd,struct msghdr *msg,unsigned int flags); int sendmsg(int sockfd,const strcut msghdr *msg,unsigned int flags);
返回值:成功——字节数;失败——(-1)
7.关闭操作
#include<unistd.h> int close(int fd);
8.其他:申请共享内存
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); 函数功能:得到一个共享内存标识符 参数含义: key:常设置为IPC_PRIVATE(0),建立新共享内存对象,大于0的32位整数:视参数shmflg来确定操作。 size:新建的共享内存大小,以字节为单位 shmflg:根据如下进行设置 0:取共享内存标识符,若不存在则函数会报错; IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符; IPC_CREAT|IPC_EXCL:如果内核中不存在键值 与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错; 共享内存的存取权限:S_IRUSR|S_IWUSR...
shmget
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg); 函数功能:映射共享内存区到调用该函数的进程地址空间 参数含义: shmid:共享内存标识符,由shmget函数产生 shmaddr:通常指定为NULL(0)让内核自己决定一个合适的地址位置 shmflg:如果设置为SHM_RDONLY-只读模式,其他值为读写模式 成功:返回共享内存地址
shmat
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr); 函数功能:断开共享内存连接 参数分析: shmaddr:要断开共享内存的起始地址
shmdt
三、socket编程框架与示例
1.TCP网络编程设计:TCP并发服务器设计——每一个客户机的请求由服务器创建的一个子进程/线程来处理,不是用服务器进程来处理(TCP循环服务器:一次只能处理一个客户机额请求)。优点:可以“同时”响应多个客户端的服务请求。
代码功能实现:客户端A和B通过服务端进行通信
1 //编译指令:gcc -o server server.c 2 //执行指令:./server 3 #include<stdio.h> 4 #include<stdlib.h> 5 #include<sys/types.h> //数据类型定义 6 #include<sys/stat.h> 7 #include<netinet/in.h> //定义数据结构sockaddr_in 8 #include<sys/socket.h> //提供socket函数及数据结构 9 #include<string.h> 10 #include<unistd.h> 11 #include<signal.h> 12 #include<sys/ipc.h> 13 #include<errno.h> 14 #include<sys/shm.h> 15 #include<time.h> 16 #define PERM S_IRUSR|S_IWUSR 17 #define MYPORT 5000 //宏定义定义通信端口 18 #define MAX_CLIENT 5 //宏定义,定义服务程序可以连接的最大客户数量 19 #define WELCOME "chating room:" //宏定义,当客户端连接服务端时,想客户发送此欢迎字符串 20 21 //转换函数,将int类型转换成char *类型 22 static void itoa(int i,char*string) 23 { 24 int power,j; 25 j=i; 26 for(power=1;j>=10;j/=10) 27 power*=10; 28 for(;power>0;power/=10) 29 { 30 *string++=‘0‘+i/power; 31 i%=power; 32 } 33 *string=‘\0‘; 34 } 35 36 //得到当前系统时间,并将时间转换成字符串形式存放time_str指向处。 37 void get_cur_time(char * time_str) 38 { 39 time_t timep; 40 struct tm *p_curtime; 41 char *time_tmp; 42 time_tmp=(char *)malloc(2); 43 memset(time_tmp,0,2); 44 memset(time_str,0,20); 45 time(&timep); 46 p_curtime = localtime(&timep); 47 strcat(time_str," ("); 48 itoa(p_curtime->tm_hour,time_tmp); 49 strcat(time_str,time_tmp); 50 strcat(time_str,":"); 51 itoa(p_curtime->tm_min,time_tmp); 52 strcat(time_str,time_tmp); 53 strcat(time_str,":"); 54 itoa(p_curtime->tm_sec,time_tmp); 55 strcat(time_str,time_tmp); 56 strcat(time_str,")"); 57 free(time_tmp); 58 } 59 60 //创建共享存储区 61 key_t shm_create() 62 { 63 key_t shmid; //key_t实际上是int型 64 if((shmid = shmget(IPC_PRIVATE,1024,PERM)) == -1) 65 { 66 fprintf(stderr,"Create Share Memory Error:%s\n\a",strerror(errno)); 67 exit(1); 68 } 69 return shmid; //返回共享内存的标识符 70 } 71 72 //端口绑定函数:创建套接字,并绑定到指定端口 73 int bindPort(unsigned short int port) 74 { 75 int sockfd; 76 struct sockaddr_in my_addr; 77 sockfd = socket(AF_INET,SOCK_STREAM,0);//创建基于流套接字 78 my_addr.sin_family = AF_INET;//IPv4协议族 79 my_addr.sin_port = htons(port);//端口转换 80 my_addr.sin_addr.s_addr = INADDR_ANY; 81 bzero(&(my_addr.sin_zero),0); 82 if(bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr)) == -1) 83 { 84 perror("bind"); 85 exit(1); 86 } 87 printf("bing success!\n"); 88 return sockfd; 89 } 90 91 int main(int argc, char *argv[]) 92 { 93 int sockfd,clientfd,sin_size,recvbytes; //定义监听套接字、客户套接字 94 pid_t pid,ppid; 95 char *buf, *r_addr, *w_addr, *temp, *time_str;//="\0"; //定义临时存储区 96 struct sockaddr_in their_addr; //定义地址结构 97 key_t shmid; 98 shmid = shm_create(); //创建共享存储区 99 temp = (char *)malloc(255); 100 time_str=(char *)malloc(20); 101 102 sockfd = bindPort(MYPORT);//绑定端口 103 104 while(1) 105 { 106 if(listen(sockfd,MAX_CLIENT) == -1)//在指定端口上监听 107 { 108 perror("listen"); 109 exit(1); 110 } 111 printf("listening......\n"); 112 if((clientfd = accept(sockfd,(struct sockaddr*)&their_addr,&sin_size)) == -1)//接收客户端连接 113 { 114 perror("accept"); 115 exit(1); 116 } 117 printf("accept from:%d\n",inet_ntoa(their_addr.sin_addr)); 118 send(clientfd,WELCOME,strlen(WELCOME),0);//发送问候信息 119 buf = (char *)malloc(255); 120 ppid = fork(); //创建子进程 121 if(ppid == 0) 122 { 123 pid = fork(); //创建子进程 -- TCP并发服务器 124 while(1) 125 { 126 if(pid > 0) 127 { 128 //父进程:接收信息 129 memset(buf,0,255); 130 if((recvbytes = recv(clientfd,buf,255,0)) <= 0) 131 { 132 perror("recv1"); 133 close(clientfd); 134 raise(SIGKILL); 135 exit(1); 136 } 137 //write buf‘s data to share memory 138 w_addr = shmat(shmid, 0, 0); 139 memset(w_addr, ‘\0‘, 1024); 140 strncpy(w_addr, buf, 1024); 141 get_cur_time(time_str); 142 strcat(buf,time_str); 143 printf(" %s\n",buf); 144 } 145 else if(pid == 0) 146 { 147 //子进程用于发送信息 148 sleep(1); 149 r_addr = shmat(shmid, 0, 0); 150 if(strcmp(temp,r_addr) != 0) 151 { 152 strcpy(temp,r_addr); 153 get_cur_time(time_str); 154 strcat(r_addr,time_str); 155 if(send(clientfd,r_addr,strlen(r_addr),0) == -1) 156 { 157 perror("send"); 158 } 159 memset(r_addr, ‘\0‘, 1024); 160 strcpy(r_addr,temp); 161 } 162 } 163 else 164 perror("fork"); 165 } 166 } 167 } 168 printf("------------------------------\n"); 169 free(buf); 170 close(sockfd); 171 close(clientfd); 172 return 0; 173 }
server.c
1 //编译指令:gcc -o client client.c 2 //执行指令:./client 192.168.1.107 5000 user_name 3 #include<stdio.h> 4 #include<netinet/in.h> //定义数据结构sockaddr_in 5 #include<sys/socket.h> //提供socket函数及数据结构 6 #include<sys/types.h> //数据类型定义 7 #include<string.h> 8 #include<stdlib.h> 9 #include<netdb.h> 10 #include<unistd.h> 11 #include<signal.h> 12 #include<time.h> 13 14 int main(int argc, char *argv[]) 15 { 16 struct sockaddr_in clientaddr;//定义地址结构 17 pid_t pid; 18 int clientfd,sendbytes,recvbytes;//定义客户端套接字 19 struct hostent *host; 20 char *buf,*buf_r; 21 if(argc < 4) 22 { 23 printf("usage:\n"); 24 printf("%s host port name\n",argv[0]); 25 exit(1); 26 } 27 host = gethostbyname(argv[1]); 28 if((clientfd = socket(AF_INET,SOCK_STREAM,0)) == -1) //创建客户端套接字 29 { 30 perror("socket\n"); 31 exit(1); 32 } 33 //绑定客户端套接字 34 clientaddr.sin_family = AF_INET; 35 clientaddr.sin_port = htons((uint16_t)atoi(argv[2])); 36 clientaddr.sin_addr = *((struct in_addr *)host->h_addr); 37 bzero(&(clientaddr.sin_zero),0); 38 if(connect(clientfd,(struct sockaddr *)&clientaddr,sizeof(struct sockaddr)) == -1) //连接服务端 39 { 40 perror("connect error.\n"); 41 exit(1); 42 } 43 buf=(char *)malloc(120); 44 memset(buf,0,120); 45 buf_r=(char *)malloc(100); 46 if( recv(clientfd,buf,100,0) == -1) 47 { 48 perror("recv:"); 49 exit(1); 50 } 51 printf("\n%s\n",buf); 52 pid = fork();//创建子进程 53 while(1) 54 { 55 if(pid > 0) 56 { 57 //父进程:发送信息 58 strcpy(buf,argv[3]); 59 strcat(buf,":"); 60 memset(buf_r,0,100); 61 fgets(buf_r,100,stdin); //从标准输入获取 62 strncat(buf,buf_r,strlen(buf_r)-1); //连接两个字符串 63 if((sendbytes = send(clientfd,buf,strlen(buf),0)) == -1) 64 { 65 perror("send\n"); 66 exit(1); 67 } 68 } 69 else if(pid == 0) 70 { 71 //子进程:接收信息 72 memset(buf,0,100); 73 if(recv(clientfd,buf,100,0) <= 0) 74 { 75 perror("recv:"); 76 close(clientfd); 77 raise(SIGSTOP); 78 exit(1); 79 } 80 printf("%s\n",buf); 81 } 82 else 83 perror("fork error."); 84 } 85 close(clientfd); 86 return 0; 87 }
client.c
提示:运行测试程序时,客户端A/B和服务端分别在一个终端页面进行。
2.UDP网络编程设计:UDP循环服务器设计——server每一次从socket上获得client的请求,然后进行处理,再把结果返回给client,如此循环。缺点:如果一个客户端一直占有这个服务端,其他的客户端就不能获得服务端的服务。
1 //编译指令:gcc -o server server.c 2 //运行指令:./server 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include <errno.h> 6 #include <string.h> 7 #include <unistd.h> 8 #include <netdb.h> 9 #include <sys/socket.h> 10 #include <netinet/in.h> 11 #include <sys/types.h> 12 #include <arpa/inet.h> 13 14 #define SERVER_PORT 5001 15 #define MAX_MSG_SIZE 1024 16 17 int main(void) 18 { 19 int sockfd; 20 struct sockaddr_in addr,addr1; 21 int addrlen,n; 22 char msg[MAX_MSG_SIZE]; 23 24 /* 服务器端建立socket描述符 */ 25 sockfd=socket(AF_INET,SOCK_DGRAM,0); 26 if(sockfd<0) 27 { 28 fprintf(stderr,"Socket Error:%s\n",strerror(errno)); 29 exit(1); 30 } 31 32 bzero(&addr,sizeof(struct sockaddr_in)); 33 addr.sin_family=AF_INET; 34 addr.sin_addr.s_addr=htonl(INADDR_ANY); 35 addr.sin_port=htons(SERVER_PORT); 36 37 if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in))<0) 38 { 39 fprintf(stderr,"Bind Error:%s\n",strerror(errno)); 40 exit(1); 41 } 42 43 while(1) 44 { 45 bzero(msg,sizeof(msg)); 46 addrlen = sizeof(struct sockaddr); 47 n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,(struct sockaddr*)&addr1,&addrlen); // 从客户端接收消息 48 msg[n]=0;//添加上字符串结束标志 49 fprintf(stdout,"Server have received %s",msg); // 显示消息 50 } 51 52 close(sockfd); 53 }
server.c
1 //编译指令:gcc -o client client.c 2 //运行指令:./client 192.168.1.107 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include <errno.h> 6 #include <string.h> 7 #include <unistd.h> 8 #include <netdb.h> 9 #include <sys/socket.h> 10 #include <netinet/in.h> 11 #include <sys/types.h> 12 #include <arpa/inet.h> 13 14 #define SERVER_PORT 5001 15 #define MAX_BUF_SIZE 1024 16 17 int main(int argc,char *argv[]) 18 { 19 int sockfd; 20 struct sockaddr_in addr; 21 char buffer[MAX_BUF_SIZE]; 22 int n; 23 24 if(argc!=2) 25 { 26 fprintf(stderr,"Usage:%s server_ip\n",argv[0]); 27 exit(1); 28 } 29 30 /* 建立 sockfd描述符 */ 31 sockfd=socket(AF_INET,SOCK_DGRAM,0); 32 if(sockfd<0) 33 { 34 fprintf(stderr,"Socket Error:%s\n",strerror(errno)); 35 exit(1); 36 } 37 38 bzero(&addr,sizeof(struct sockaddr_in)); 39 addr.sin_family=AF_INET; 40 addr.sin_port=htons(SERVER_PORT); 41 if(inet_aton(argv[1],&addr.sin_addr)<0) //inet_aton函数用于把字符串型的IP地址转化成网络二进制数字 42 { 43 fprintf(stderr,"Ip error:%s\n",strerror(errno)); 44 exit(1); 45 } 46 47 while(1) 48 { /* 从键盘读入,写到服务端 */ 49 printf("Please input char:\n"); 50 fgets(buffer,MAX_BUF_SIZE,stdin); 51 sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr *)(&addr),sizeof(struct sockaddr_in)); 52 bzero(buffer,MAX_BUF_SIZE); 53 } 54 55 close(sockfd); 56 }
client.c