前言
主要是Linux网络程序和多线程程序的编写,实现一个网络访问的LED矩阵显示器。客户端采用telnet进行连接。
socket的编程主要和计网实验课上的差不多,计网实验socket编程传送门
字符串的显示
在上次显示一个字符的基础上进行一个字符串的显示。字符的显示
对于字符串的显示可以直接写入一个字符串,在内核中采用内核队列进行实现。或者每次只能写入一个字符,在应用程序进行一个循环写入。由于上次没有实现内核队列的字符串显示,这里就采用了应用程序循环进行实现。
在上次的基础上,编写一个Display函数,传入值为一个字符串,每次只要将一个字符写入之后,延时500ms之后,不断写入即可。
1 //Display a string on the MAX7219 2 int Display(char str[]) 3 { 4 int fd; 5 int ret; 6 7 fd = open("/dev/MAX7219", O_WRONLY); 8 if (fd < 0) 9 { 10 fprintf(stderr, "Fail to open /dev/MAX7219!\n"); 11 return -1; 12 } 13 int i=0; 14 while(str[i]) 15 { 16 if((ret = write(fd, &str[i], 1))<0) 17 { 18 fprintf(stderr, "Fail to write /dev/MAX7219! %d\n", ret); 19 return -1; 20 } 21 ++i; 22 Delay_xms(500); 23 } 24 fprintf(stdout, "Write /dev/MAX7219 successfully! %d\n", ret); 25 close(fd); 26 return 0; 27 }
多线程Socket程序编写
服务端socket的创建
Socket程序的编写和计网的实验原理基本相同。对于实现多个客户端的连接可以采用IO复用的方式(Select)或者采用Linux下的Pthread库的多线程进行编写,每个线程对应一个客户端。这里采用的服务端多线程,主程序主要负责建立客户端后,阻塞的Accept客户端连接,当Accept一个客户端的一个连接之后就创建一个线程处理该连接。对于多线程编写,需要注意处理好多线程中的数据冲突问题。
首先在主程序建立服务端Socket连接,其过程如下:
- 调用socket函数建立一个socket,其中传入的参数AF_INET参数表示其采用TCP协议,并且调用Linux系统提供的fcntl函数设置这个socket为非阻塞。
- 然后初始化服务的地址,保护监听的IP(这里设置的监听服务器端所有的网卡),设置相应的端口以及协议类型(TCP),调用bind函数将其和前面的建立的socket绑定。
- 设置该socket开始监听,这主要对应TCP连接上面的第一次握手,将TCP第一次握手成功的放在队列(SYN队列)中,其中传入的参数BACKLOG为Socket中队列的长度。
- 然后调用Accept函数进行接收客户端的请求,其主要是从TCP上次握手成功的Accept队列中将客户端信息去除进行处理。
1 int listenfd; 2 struct sockaddr_in servaddr; 3 //create a socket 4 if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 5 { 6 fprintf(stderr, "Create socket error: %s(errno: %d)\n", strerror(errno), errno); 7 exit(0); 8 } 9 else 10 fprintf(stdout, "Create a socket successfully\n"); 11 //set the server address 12 memset(&servaddr, 0, sizeof(servaddr)); //initialize the server address 13 servaddr.sin_family = AF_INET; //AF_INET means using TCP protocol 14 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //any in address(there may more thaonenetwork card in the server) 15 servaddr.sin_port = htons(PORT); //set the port 16 17 //bind the server address with the socket 18 if(bind(listenfd, (struct sockaddr*)(&servaddr), sizeof(servaddr)) == -1) 19 { 20 fprintf(stderr, "Bind socket error: %s(errno: %d)\n", strerror(errno), errno); 21 exit(0); 22 } 23 else 24 fprintf(stdout, "Bind socket successfully\n"); 25 26 //listen 27 if(listen(listenfd, BACKLOG) == -1) 28 { 29 fprintf(stderr, "Listen socket error: %s(errno: %d)\n", strerror(errno), errno); 30 exit(0); 31 } 32 else 33 fprintf(stdout, "Listen socket successfully\n");
客户端对应线程的创建
服务端调用Accept函数进行接收一个客户端的信息,如果存在连接上的客户端,则为其分配一个线程位置,然后调用pthread_create创建一个线程,并且将该客户端的信息参数传入线程。这里采用一个结构体的方式记录了客户端的相关信息,包括客户端IP,端口号,编号,是否连接等等。
这里为了防止服务器的无限的创建线程,设置了最大同时在线的客户端数目(MAXCONN),如果新连接上的客户端发现已经操作上线,那么该主线程就会睡眠,其等待在了一个客户端断开连接的信号量上面,如果有客户端断开连接,则主线程被唤醒,接受该客户端的连接。
需要注意的是这里涉及到很多的多线程的操作,很有可能发生数据冲突,需要很好使用pthread中的互斥锁防止发生数据冲突。
1 while(1) 2 { 3 pthread_mutex_lock(&activeConnMutex); 4 if(activeConn >= MAXCONN) 5 pthread_cond_wait(&connDis, &activeConnMutex); //wait on a signal 6 pthread_mutex_unlock(&activeConnMutex); 7 8 //find an empty postion for a new connnetion 9 int i=0; 10 while(i<MAXCONN) 11 { 12 pthread_mutex_lock(&clientsMutex[i]); 13 if(!clients[i].isConn) 14 { 15 pthread_mutex_unlock(&clientsMutex[i]); 16 break; 17 } 18 pthread_mutex_unlock(&clientsMutex[i]); 19 ++i; 20 } 21 22 //accept 23 struct sockaddr_in addr; 24 int clientfd; 25 int sin_size = sizeof(struct sockaddr_in); 26 if((clientfd = accept(listenfd, (struct sockaddr*)(&addr), &sin_size)) == -1) 27 { 28 sleep(1); 29 continue; 30 } 31 else 32 fprintf(stdout, "Accept socket successfully\n"); 33 34 pthread_mutex_lock(&clientsMutex[i]); 35 clients[i].clientfd = clientfd; 36 clients[i].addr = addr; 37 clients[i].isConn = 1; 38 clients[i].index = i; 39 pthread_mutex_unlock(&clientsMutex[i]); 40 41 //create a thread for a client 42 pthread_create(&threadID[i], NULL, (void *)clientManager, &clients[i]); 43 44 } //end-while
客户端服务线程
在服务器创建了一个线程之后就会调用clientManager这个函数对于该连接进行处理。对于该线程,其主要是阻塞着等待在recv函数上面,接受客户端发送来的字符串,然后调用上面的Display函数将该字符串显示在MAX7219上面。
这里采用了一个Write的互斥锁,将其加在了Display函数前后,这样就保证了多个客户端连接的时候,只有在等一个客户端的字符串显示完成之后,另外一个客户端的字符串才会被显示,不会造成多个客户端的字符串交错显示的情况。
1 void clientManager(void* argv) 2 { 3 ClientInfo *client = (ClientInfo *)(argv); 4 5 BYTE buff[BUFFSIZE]; 6 int recvbytes; 7 8 int i=0; 9 int clientfd = client->clientfd; 10 struct sockaddr_in addr = client->addr; 11 int isConn = client->isConn; 12 int clientIndex = client->index; 13 //receive the string sent from the client 14 while((recvbytes = recv(clientfd, buff, BUFFSIZE, 0)) != -1) 15 { 16 buff[recvbytes] = ‘\0‘; 17 pthread_mutex_lock(&writeMutex); 18 Display(buff); 19 pthread_mutex_unlock(&writeMutex); 20 } //end while 21 22 pthread_mutex_lock(&clientsMutex[clientIndex]); 23 client->isConn = 0; 24 pthread_mutex_unlock(&clientsMutex[clientIndex]); 25 pthread_cond_signal(&connDis); //send a disconnetion signal and the waiting client can get response 26 if(close(clientfd) == -1) 27 fprintf(stderr, "Close %d client eroor: %s(errno: %d)\n", clientfd, strerror(errno), errno); 28 fprintf(stderr, "Client %d connetion is closed\n", clientfd); 29 30 pthread_exit(NULL); 31 }
Telnet连接显示
将程序在树莓派上编译之后运行,可以看到服务器端的socket创建,绑定以及监听成功。然后在Ubuntu上用telnet去连接,这里用了三个客户端进行连接。可以看到服务端接受了三个客户端的连接,然后在三个客户端分别发送字符串,发现其字符串分别在MAX7219上依次正常显示。
附(完整代码):
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/ioctl.h> 5 #include <sys/time.h> 6 #include <sys/fcntl.h> 7 #include <sys/stat.h> 8 #include <sys/types.h> 9 #include <sys/socket.h> 10 #include <netinet/in.h> 11 #include <arpa/inet.h> 12 #include <errno.h> 13 #include <string.h> 14 #include <pthread.h> 15 16 17 #define PORT 8888 18 #define BACKLOG 10 19 #define MAXCONN 100 20 #define BUFFSIZE 1024 21 22 typedef unsigned char BYTE; 23 typedef struct ClientInfo 24 { 25 struct sockaddr_in addr; 26 int clientfd; 27 int isConn; 28 int index; 29 } ClientInfo; 30 31 pthread_t threadID[MAXCONN]; 32 pthread_mutex_t activeConnMutex; 33 pthread_mutex_t clientsMutex[MAXCONN]; 34 pthread_cond_t connDis; 35 pthread_mutex_t writeMutex; 36 37 ClientInfo clients[MAXCONN]; 38 39 void Delay_xms(uint x) 40 { 41 uint i,j; 42 for(i=0;i<x;i++) 43 for(j=0;j<50000;j++); 44 } 45 46 int Display(char str[]) 47 { 48 int fd; 49 int ret; 50 51 fd = open("/dev/MAX7219", O_WRONLY); 52 if (fd < 0) 53 { 54 fprintf(stderr, "Fail to open /dev/MAX7219!\n"); 55 return -1; 56 } 57 int i=0; 58 while(str[i]) 59 { 60 if((ret = write(fd, &str[i], 1))<0) 61 { 62 fprintf(stderr, "Fail to write /dev/MAX7219! %d\n", ret); 63 return -1; 64 } 65 ++i; 66 Delay_xms(500); 67 } 68 fprintf(stdout, "Write /dev/MAX7219 successfully! %d\n", ret); 69 close(fd); 70 return 0; 71 } 72 73 void clientManager(void* argv) 74 { 75 ClientInfo *client = (ClientInfo *)(argv); 76 77 BYTE buff[BUFFSIZE]; 78 int recvbytes; 79 80 int i=0; 81 int clientfd = client->clientfd; 82 struct sockaddr_in addr = client->addr; 83 int isConn = client->isConn; 84 int clientIndex = client->index; 85 86 while((recvbytes = recv(clientfd, buff, BUFFSIZE, 0)) != -1) 87 { 88 buff[recvbytes] = ‘\0‘; 89 pthread_mutex_lock(&writeMutex); 90 Display(buff); 91 pthread_mutex_unlock(&writeMutex); 92 } //end while 93 94 pthread_mutex_lock(&clientsMutex[clientIndex]); 95 client->isConn = 0; 96 pthread_mutex_unlock(&clientsMutex[clientIndex]); 97 98 pthread_cond_signal(&connDis); //send a disconnetion signal and the waiting client can get response 99 100 if(close(clientfd) == -1) 101 fprintf(stderr, "Close %d client eroor: %s(errno: %d)\n", clientfd, strerror(errno), errno); 102 fprintf(stderr, "Client %d connetion is closed\n", clientfd); 103 104 pthread_exit(NULL); 105 } 106 107 108 int main() 109 { 110 int activeConn = 0; 111 112 //initialize the mutex 113 pthread_mutex_init(&activeConnMutex, NULL); 114 pthread_mutex_init(&writeMutex, NULL); 115 pthread_cond_init(&connDis, NULL); 116 int i=0; 117 for(;i<MAXCONN;++i) 118 pthread_mutex_init(&clientsMutex[i], NULL); 119 120 for(i=0;i<MAXCONN;++i) 121 clients[i].isConn = 0; 122 123 int listenfd; 124 struct sockaddr_in servaddr; 125 126 //create a socket 127 if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 128 { 129 fprintf(stderr, "Create socket error: %s(errno: %d)\n", strerror(errno), errno); 130 exit(0); 131 } 132 else 133 fprintf(stdout, "Create a socket successfully\n"); 134 135 //set the server address 136 memset(&servaddr, 0, sizeof(servaddr)); //initialize the server address 137 servaddr.sin_family = AF_INET; //AF_INET means using TCP protocol 138 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //any in address(there may more than one network card in the server) 139 servaddr.sin_port = htons(PORT); //set the port 140 141 //bind the server address with the socket 142 if(bind(listenfd, (struct sockaddr*)(&servaddr), sizeof(servaddr)) == -1) 143 { 144 fprintf(stderr, "Bind socket error: %s(errno: %d)\n", strerror(errno), errno); 145 exit(0); 146 } 147 else 148 fprintf(stdout, "Bind socket successfully\n"); 149 150 //listen 151 if(listen(listenfd, BACKLOG) == -1) 152 { 153 fprintf(stderr, "Listen socket error: %s(errno: %d)\n", strerror(errno), errno); 154 exit(0); 155 } 156 else 157 fprintf(stdout, "Listen socket successfully\n"); 158 159 while(1) 160 { 161 pthread_mutex_lock(&activeConnMutex); 162 if(activeConn >= MAXCONN) 163 pthread_cond_wait(&connDis, &activeConnMutex); //wait on a signal 164 pthread_mutex_unlock(&activeConnMutex); 165 166 //find an empty postion for a new connnetion 167 int i=0; 168 while(i<MAXCONN) 169 { 170 pthread_mutex_lock(&clientsMutex[i]); 171 if(!clients[i].isConn) 172 { 173 pthread_mutex_unlock(&clientsMutex[i]); 174 break; 175 } 176 pthread_mutex_unlock(&clientsMutex[i]); 177 ++i; 178 } 179 180 //accept 181 struct sockaddr_in addr; 182 int clientfd; 183 int sin_size = sizeof(struct sockaddr_in); 184 if((clientfd = accept(listenfd, (struct sockaddr*)(&addr), &sin_size)) == -1) 185 { 186 sleep(1); 187 continue; 188 } 189 else 190 fprintf(stdout, "Accept socket successfully\n"); 191 192 pthread_mutex_lock(&clientsMutex[i]); 193 clients[i].clientfd = clientfd; 194 clients[i].addr = addr; 195 clients[i].isConn = 1; 196 clients[i].index = i; 197 pthread_mutex_unlock(&clientsMutex[i]); 198 199 //create a thread for a client 200 pthread_create(&threadID[i], NULL, (void *)clientManager, &clients[i]); 201 202 } //end-while 203 }