文章目录
-
-
- 一、实验说明
- 二、面向连接的流式套接字 C/S 例子
- 三、非阻塞的多人聊天服务器端例子
- 四、简单的 IPv6 UDP socket编程
- 四、参考书籍和??
-
一、实验说明
- 服务端:树莓派(可以使用putty、xshell、vnc远程操作树莓派,这里使用vnc)
- 客户端:Ubuntu
- 所用语言:C语言
- 树莓派和Ubuntu应处于同一个局域网下(可以用手机热点连接)
二、面向连接的流式套接字 C/S 例子
-
在树莓派下,新建一个 Server1.c,命令
nano Server1.c ,然后写入如下内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103#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;
} -
然后编译并运行
1
2gcc Server1.c -o Server1
./Server1 -
在Ubuntu系统下,新建一个Client1.c文件,命令
gedit Client1.c ,然后写入如下内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78#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;
} -
然后编译并运行
1
2gcc Client1.c -o Client1
./Client1 192.168.43.161注意:这里后面跟的IP是自己服务端的IP,即,自己树莓派的IP
-
运行结果如下
三、非阻塞的多人聊天服务器端例子
-
在树莓派下,新建一个Server2.c文件,命令
nano Server2.c ,然后写入如下内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173#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;
} -
编译并运行
1
2gcc Server2.c -o Server2
./Server2 -
在Ubuntu下新建一个Client2.c文件,命令
gedit Client2.c ,然后写入如下内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108#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的虚拟机,重复客户端的操作
-
编译并运行
1
2gcc -pthread Client2.c -o Client2
./Client2 192.168.43.161 -
运行结果如下
① 客户端
② 服务端
四、简单的 IPv6 UDP socket编程
-
在树莓派和Ubuntu下查看自己的IPv6地址地址,
ifconfig ,然后再看双方能不能ping通 -
在树莓派下新建一个Server4.c文件,命令
nano Server4.c ,然后写入如下内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62#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;
} -
编译运行
1
2gcc Server4.c -o server4
./server4 2409:8960:1e48:f1a:b6a4:81d5:7021:4873 9090 -
再Ubuntu下,新建一个Client4.c文件,然后写入如下内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50#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;
} -
编译运行
1
2gcc Client4.c -o Client4
./Client4 2409:8960:1e48:f1a:b6a4:81d5:7021:4873 9090 -
运行结果如下
① 客户端
② 服务端
-
wireshark抓包
四、参考书籍和??
- Ubuntu 与树莓派之间的两个 socket 应用实例:C/S 和多人聊天
- 《网络编程技术》
- 简单的IPv6 UDP/TCP socket编程 – 两台Linux实现简单的ipv6通信