总结 Linux 下网络编程的知识点,梳理 TCP 服务器、TCP 客户端创建流程,列出网络编程常见的练习题。后面两个小节介绍 Linux 下线程编程的知识点,介绍子线程的创建方式、属性设置、完成运行,在结合子线程、select 配合 TCP 服务器处理客户端的连接。
TCP 服务器创建流程:
- 创建套接字 
- 绑定 IP 地址和端口号(创建服务器) 
- 设置监听的数量(限制最大可以连接的客户端数量) 
- 等待客户端连接 
- 实现基本通信 
TCP 客户端创建流程
- 创建套接字 
- 连接服务器 
- 实现基本通信 
任务 1:网络编程
练习:
【1】实现 TCP 服务器与 TCP 客户端之间的基本通信,收发数据 (按照上课的思路流程看函数文档)
【2】实现 TCP 服务器与 TCP 客户端之间的文件传输。(单个文件传输)
验证方式:(1) 同一台电脑演示 (2)同桌之间演示
考虑的问题:
(1) 网络的传输环境,考虑应答问题
(2) 数据丢包之后如何处理? 可以重发
(3) 超时处理
(4) 服务器与客户端之间连接断开处理。(客户端和服务器两边都需要重新连接)
文件传输可以在广告机中使用。
(扩展要求): 显示接收进度百分比,显示接收的文件名称,推荐: 定义结构体(使用数据结构)
- (扩展)实现 TCP 服务器与 TCP 客户端之间的目录传输。 
- (扩展)实现网络聊天室(模仿 QQ 发送消息的效果) 
一般情况下,推荐最大每次传输的字节数不超过 1024 字节。
任务 2:线程编程
 #include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);Compile and link with -pthread.-lpthread
   复制代码
 
练习:
【1】学习线程的基本使用
 #include <stdio.h>#include <pthread.h>
void *start_1(void *arg){  while(1)  {    printf("123\n");    sleep(1);  }}
void *start_2(void *arg){  while(1)  {    printf("456\n");    sleep(1);  }}
int main(int argc,char *argv[]){  pthread_t thread_1;  pthread_t thread_2;  pthread_create(&thread_1,NULL,start_1,NULL);  pthread_create(&thread_2,NULL,start_2,NULL);  while(1)  {    printf("789\n");    sleep(1);  }  return 0;}
   复制代码
 
【2】实现一个服务器实现多个客户端的连接,实现通信。
思路: 一个客户端就是一个独立的线程。
【3】扩展练习: 实现服务器同时对多个客户端进行文件发送。
(1) 服务器连接上一个客户端就创建一个线程。
(2) 线程的函数需要写几个? 1 个
1 个函数需要考虑的问题: 函数的可重入性能!
需要考虑到资源抢占! 使用信号量!
(抢答器)
设置线程分离属性:
 #include <stdio.h>#include <pthread.h>
char str1[]="123456";char str2[]="abcdef";void *start_1(void *arg){   printf("arg1=%s\n",arg);   sleep(1);}
void *start_2(void *arg){  printf("arg2=%s\n",arg);  sleep(2);}
int main(int argc,char *argv[]){  pthread_t thread_1;  pthread_t thread_2;  pthread_create(&thread_1,NULL,start_1,"线程1的参数传递测试");  pthread_create(&thread_2,NULL,start_2,"线程2的参数传递测试");    pthread_detach(thread_1); //设置线程的分离属性  pthread_detach(thread_2); //设置线程的分离属性    while(1)  {      }  return 0;}
   复制代码
 任务 3: select 阻塞轮询机制
(1) 服务器什么时候收到数据? read
(2) 客户端什么时候收到数据? Read
(3) 客户端如何判断已经与服务器断开连接?
使用 select 机制 ,当 select 函数返回值为 1,read 函数为 0 就表示断开
(4) 服务器如何检测客户端已经断开连接?
采用心跳包的模式: 规定客户端每 5 秒钟发送一个特定的数据给服务器。
IO 多路复用是指内核一旦发现进程指定的一个或者多个 IO 条件准备读取,它就通知该进程。
IO 条件:
(1) 网络编程中的读写
(2) 标准输出输入中的读写
 #include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
select:同时可以监控多个文件描述符。
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数:
int nfds :最大的文件描述符+1
fd_set *readfds :读事件发生
fd_set *writefds :写事件发生
fd_set *exceptfds:出现问题
struct timeval *timeout:轮询的时间。
填NULL表示无限阻塞。
结构体里的成员填0,表示不阻塞
结构体里的成员填>0,正常的阻塞时间
返回值: 0表示没有任何事件发生,负数表示失败。>0表示发生对应的事件。
void FD_CLR(int fd, fd_set *set); //清除指定文件描述符
int FD_ISSET(int fd, fd_set *set); //检测指定的文件描述符是否发生了事件
void FD_SET(int fd, fd_set *set); //添加指定的文件描述符到fd描述符集合(多次调用)
void FD_ZERO(fd_set *set); //清除整个文件描述符集合
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
   复制代码
 
TCP 服务器端处理:
 #include <stdio.h>#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>#include <arpa/inet.h>   //使用大小端转换函数#include <string.h>#include <sys/select.h>#include <sys/time.h>#include <sys/types.h>#include <unistd.h>#include <pthread.h>
//函数声明void *start_routine_1(void *dev);void *start_routine_2(void *dev);
typedef void *(*start_routine) (void *);start_routine fun[]={start_routine_1,start_routine_2};  pthread_t thread_id[2];                //存放线程的标识符int clientfd[2];                       //保存TCP客户端的网络套接字struct sockaddr_in client_address[2];  //存放客户端的信息socklen_t address_len[2];              //存放客户端结构体信息的长度
/*服务器端口号定义*/#define P_host 8080
/*TCP服务器代码*/int main(int argc,char *argv[]){   int socketfd;   struct sockaddr_in server_address;     //存放服务器的IP地址信息      memset(&server_address,0,sizeof(struct sockaddr_in)); //初始化内存空间   memset(client_address,0,sizeof(struct sockaddr_in)*2); //初始化内存空间      server_address.sin_family=PF_INET;            //IPV4协议   server_address.sin_port=htons(P_host);        //端口号赋值   server_address.sin_addr.s_addr=INADDR_ANY;    //本地IP地址  
  /*1 .创建套接字*/   socketfd=socket(PF_INET,SOCK_STREAM,0);   if(socketfd<0)     {         printf("服务器网络套接字创建失败!\n");           return -1;     }            /*2. 绑定端口,创建服务器*/   if(bind(socketfd,(const struct sockaddr *)&server_address,sizeof(struct sockaddr))!=0)     {         printf("服务器绑定端口失败!\n");           return -1;        }      /*3. 设监听的端口数量*/   if(listen(socketfd,10)!=0)     {        printf("服务器端口监听失败!\n");          return -1;        }       int i;        for(i=0;i<2;i++)     {        address_len[i]=sizeof(struct sockaddr);  //计算结构体大小 20        /*4. 等待客户端连接*/      if((clientfd[i]=accept(socketfd,(struct sockaddr *)&client_address[i],&address_len[i]))<0)       {            printf("等待客户端连接失败!\n");             break;         }             //创建线程       if(pthread_create(&thread_id[i],NULL,fun[i],NULL)!=0)      {         printf("线程_%d_创建失败!\n",i);          }        }          while(1)     {                }          //阻塞方式等待线程的结束  pthread_join(thread_id[0],NULL);  pthread_join(thread_id[1],NULL);  return 0;  }
//线程1void *start_routine_1(void *dev){   while(1)   {       printf("TCP客户端1连接!\n");       sleep(2);   }   //终止线程   pthread_exit(NULL);}
//线程2void *start_routine_2(void *dev){  while(1)  {       printf("TCP客户端2连接!\n");       sleep(2);  }   //终止线程   pthread_exit(NULL);}
   复制代码
 
评论