一、实验说明
- 服务端:树莓派(可以使用putty、xshell、vnc远程操作树莓派,这里使用vnc)
- 客户端:Ubuntu
- 所用语言:C语言
- 树莓派和Ubuntu应处于同一个局域网下(可以用手机热点连接)
二、面向连接的流式套接字 C/S 例子
-
在树莓派下,新建一个 Server1.c,命令
nano Server1.c
,然后写入如下内容#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/wait.h> #include <signal.h> #define PORT "9090" // the port users will be connecting to #define BACKLOG 10 // how many pending connections queue will hold void sigchld_handler(int s) { while(waitpid(-1, NULL, WNOHANG) > 0); } // get sockaddr, IPv4 or IPv6: void *get_in_addr(struct sockaddr *sa) { if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } return &(((struct sockaddr_in6*)sa)->sin6_addr); } int main(void) { int sockfd, new_fd; // listen on sock_fd, new connection on new_fd struct addrinfo hints, *servinfo, *p; struct sockaddr_storage their_addr; // connector's address information socklen_t sin_size; struct sigaction sa; int yes=1; char s[INET6_ADDRSTRLEN]; int rv; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // use my IP if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return 1; } // loop through all the results and bind to the first we can for(p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("server: socket"); continue; } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("setsockopt"); exit(1); } if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); perror("server: bind"); continue; } break; } if (p == NULL) { fprintf(stderr, "server: failed to bind\n"); return 2; } freeaddrinfo(servinfo); // all done with this structure if (listen(sockfd, BACKLOG) == -1) { perror("listen"); exit(1); } sa.sa_handler = sigchld_handler; // reap all dead processes sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction"); exit(1); } printf("server: waiting for connections...\n"); while(1) { // main accept() loop sin_size = sizeof their_addr; new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); if (new_fd == -1) { perror("accept"); continue; } inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s); printf("server: got connection from %s\n", s); if (!fork()) { // this is the child process close(sockfd); // child doesn't need the listener if (send(new_fd, "Hello, world!", 13, 0) == -1) perror("send"); close(new_fd); exit(0); } close(new_fd); // parent doesn't need this } return 0; }
-
然后编译并运行
gcc Server1.c -o Server1 ./Server1
-
在Ubuntu系统下,新建一个Client1.c文件,命令
gedit Client1.c
,然后写入如下内容#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #define PORT "9090" //the port client will be connecting to #define MAXDATASIZE 100 //max number of bytes we can get at once //get sockaddr, IPv4 or IPv6 void *get_in_addr(struct sockaddr *sa) { if(sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } return &(((struct sockaddr_in6*)sa)->sin6_addr); } int main(int argc, char *argv[]) { int sockfd, numbytes; char buf[MAXDATASIZE]; struct addrinfo hints, *servinfo, *p; int rv; char s[INET6_ADDRSTRLEN]; if(argc != 2) { fprintf(stderr, "usage:client hostname\n"); exit(1); } memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo:%s\n",gai_strerror(rv)); return 1; } // loop through all the results and connect to the first we can for(p = servinfo; p != NULL; p = p->ai_next) { if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("client:socket"); continue; } if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); perror("client:connect"); continue; } break; } if(p == NULL) { fprintf(stderr, "client:failed to connect\n"); return 2; } inet_ntop(p->ai_family, get_in_addr((struct sockaddr*)p->ai_addr), s, sizeof s); printf("client:connecting to %s\n",s); freeaddrinfo(servinfo);// all done with this structure if((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) { perror("recv"); exit(1); } buf[numbytes] = '\0'; printf("client:received %s\n",buf); close(sockfd); return 0; }
-
然后编译并运行
gcc Client1.c -o Client1 ./Client1 192.168.43.161
注意:这里后面跟的IP是自己服务端的IP,即,自己树莓派的IP
-
运行结果如下
三、非阻塞的多人聊天服务器端例子
-
在树莓派下,新建一个Server2.c文件,命令
nano Server2.c
,然后写入如下内容#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #define PORT "9090" //port we're listening on //get sockaddr,IPv4 or IPv6: void *get_in_addr(struct sockaddr *sa) { if(sa->sa_family == AF_INET){ return &(((struct sockaddr_in*)sa)->sin_addr); } return &(((struct sockaddr_in6*)sa)->sin6_addr); } int main(void) { fd_set master; // master file descriptor list fd_set read_fds; // temp file descriptor list for select() int fdmax; // maximum file descriptor number int listener; // listening socket descriptor int newfd; // newly accept()ed socket descriptor struct sockaddr_storage remoteaddr; // client address socklen_t addrlen; char buf[256]; // buffer for client data int nbytes; char remoteIP[INET6_ADDRSTRLEN]; int yes=1; // for setsockopt() SO_REUSEADDR, below int i, j, rv; struct addrinfo hints, *ai, *p; FD_ZERO(&master); // clear the master and temp sets FD_ZERO(&read_fds); // get us a socket and bind it memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; if((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0) { fprintf(stderr, "selectserver:%s\n", gai_strerror(rv)); exit(1); } for(p = ai; p != NULL; p = p->ai_next) { listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol); if(listener < 0) { continue; } // lose the pesky "address already in use" error message setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); if(bind(listener, p->ai_addr, p->ai_addrlen) < 0) { close(listener); continue; } break; } // if we got here, it means we didn't get bound if(p == NULL) { fprintf(stderr, "selectserver:failed to bind\n"); exit(2); } freeaddrinfo(ai); // all done with this // listen if(listen(listener, 10) == -1) { perror("listen"); exit(3); } // add the listener to the master set FD_SET(listener, &master); // keep track of the biggest file descriptor fdmax = listener; // so far, it's this one // main loop for(;;) { read_fds = master; // copy it if(select(fdmax + 1, &read_fds, NULL, NULL, NULL) == -1) { perror("select"); exit(4); } // run through the existing connections looking for data to read for(i = 0; i <= fdmax; i++) { if(FD_ISSET(i, &read_fds))// we got one!! { if(i == listener) { // handle new connections addrlen = sizeof remoteaddr; newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen); if(newfd == -1) { perror("accept"); } else { FD_SET(newfd, &master); // add to master set if(newfd > fdmax) { // keep track of the max fdmax = newfd; } printf("selectserver: new connection from %s on " "socket %d\n", inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET6_ADDRSTRLEN), newfd); } } else { // handle data from a client if((nbytes = recv(i, buf, sizeof buf, 0)) <= 0) { // got error or connection closed by client if(nbytes == 0) { // connection closed printf("selectserver:socket %d hung up\n", i); } else { perror("recv"); } close(i);// bye! FD_CLR(i, &master);// remove from master set } else { // we got some data from a client for(j =0; j <= fdmax; j++) { // send to everyone! if(FD_ISSET(j, &master)) { // except the listener and ourselves if(j != listener && j != i) { if(send(j, buf, nbytes, 0) == -1) { perror("send"); } } } } } } //END handle from client } //END got new incoming connection } //END looping through file descriptors } //END for(;;)--and you thought it would never end! return 0; }
-
编译并运行
gcc Server2.c -o Server2 ./Server2
-
在Ubuntu下新建一个Client2.c文件,命令
gedit Client2.c
,然后写入如下内容#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #define PORT "9090" //the port client will be connecting to #define MAXDATASIZE 100 //max number of bytes we can get at once int sockfd, numbytes; char buf[MAXDATASIZE]; //get sockaddr, IPv4 or IPv6 void *get_in_addr(struct sockaddr *sa) { if(sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } return &(((struct sockaddr_in6*)sa)->sin6_addr); } void *recvMag() { while(1) { if((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) { perror("recv"); exit(1); } if(numbytes == 1) continue; buf[numbytes] = '\0'; printf("\nreceived:%s\n",buf); } } int main(int argc, char *argv[]) { //int sockfd, numbytes; //char buf[MAXDATASIZE]; struct addrinfo hints, *servinfo, *p; int rv; char s[INET6_ADDRSTRLEN]; pthread_t t1; char mag[MAXDATASIZE]; if(argc != 2) { fprintf(stderr, "usage:client hostname\n"); exit(1); } memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo:%s\n",gai_strerror(rv)); return 1; } // loop through all the results and connect to the first we can for(p = servinfo; p != NULL; p = p->ai_next) { if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("client:socket"); continue; } if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); perror("client:connect"); continue; } break; } if(p == NULL) { fprintf(stderr, "client:failed to connect\n"); return 2; } inet_ntop(p->ai_family, get_in_addr((struct sockaddr*)p->ai_addr), s, sizeof s); printf("client:connecting to %s\n",s); freeaddrinfo(servinfo);// all done with this structure int err = pthread_create(&t1, NULL, recvMag, NULL); if(err != 0) { printf("receive failed"); exit(1); } while(1) { scanf("%s", mag); if(send(sockfd, mag, sizeof mag, 0) == -1) { printf("send failed!\n"); } } return 0; }
注意:因为是一个多人聊天程序,所以这里至少应该有两个客户端,本人是重新再开个Ubuntu的虚拟机,重复客户端的操作
-
编译并运行
gcc -pthread Client2.c -o Client2 ./Client2 192.168.43.161
-
运行结果如下
① 客户端
② 服务端
四、简单的 IPv6 UDP socket编程
-
在树莓派和Ubuntu下查看自己的IPv6地址地址,
ifconfig
,然后再看双方能不能ping通 -
在树莓派下新建一个Server4.c文件,命令
nano Server4.c
,然后写入如下内容#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> #include <arpa/inet.h> int main(int argc, char **argv) { struct sockaddr_in6 s_addr; struct sockaddr_in6 c_addr; int sock; socklen_t addr_len; int len; char buff[128]; char buf_ip[128]; if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { perror("socket"); exit(errno); } else printf("create socket.\n\r"); memset(&s_addr, 0, sizeof(struct sockaddr_in6)); s_addr.sin6_family = AF_INET6; if (argv[2]) s_addr.sin6_port = htons(atoi(argv[2])); else s_addr.sin6_port = htons(4444); if (argv[1]) inet_pton(AF_INET6, argv[1], &s_addr.sin6_addr); else s_addr.sin6_addr = in6addr_any; if ((bind(sock, (struct sockaddr *) &s_addr, sizeof(s_addr))) == -1) { perror("bind"); exit(errno); } else printf("bind address to socket.\n\r"); addr_len = sizeof(c_addr); while (1) { len = recvfrom(sock, buff, sizeof(buff) - 1, 0, (struct sockaddr *) &c_addr, &addr_len); if (len < 0) { perror("recvfrom"); exit(errno); } buff[len] = '\0'; printf("receive from %s: buffer:%s\n\r", inet_ntop(AF_INET6, &c_addr.sin6_addr, buf_ip, sizeof(buf_ip)), buff); } return 0; }
-
编译运行
gcc Server4.c -o server4 ./server4 2409:8960:1e48:f1a:b6a4:81d5:7021:4873 9090
-
再Ubuntu下,新建一个Client4.c文件,然后写入如下内容
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> #include <arpa/inet.h> int main(int argc, char **argv) { struct sockaddr_in6 s_addr; int sock; int addr_len; int len; char buff[128]; if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { perror("socket"); exit(errno); } else printf("create socket.\n\r"); s_addr.sin6_family = AF_INET6; if (argv[2]) s_addr.sin6_port = htons(atoi(argv[2])); else s_addr.sin6_port = htons(4444); if (argv[1]) inet_pton(AF_INET6, argv[1], &s_addr.sin6_addr); else { printf("usage:./command ip port\n"); exit(0); } addr_len = sizeof(s_addr); strcpy(buff, "hello i'm here"); len = sendto(sock, buff, strlen(buff), 0, (struct sockaddr *) &s_addr, addr_len); if (len < 0) { printf("\n\rsend error.\n\r"); return 3; } printf("send success.\n\r"); return 0; }
-
编译运行
gcc Client4.c -o Client4 ./Client4 2409:8960:1e48:f1a:b6a4:81d5:7021:4873 9090
-
运行结果如下
① 客户端
② 服务端
-
wireshark抓包