Linux网络编程

Socket编程

套接字概念

在Linux环境下,Socket用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。既然是文件,那么我们可以使用文件描述符引用套接字,与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。

TCP/IP协议中,”IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,”IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接,因此可以用Socket来描述网络连接的一对一关系。

在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接受缓冲区。我们使用同一个文件描述符对应发送缓冲区和接收缓冲区。

网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分,那么如何定义网络数据流的地址呢。发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出。接收主机把从网络上接到的字节一次保存在接受缓冲区中,也是按内存地址从低到高的顺序保存,因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节,例如在UDP段格式中,地址0-1是16位的源端口号,如果这个端口号是1000(0x3e8),则地址0是0x03,地址1时是0xe8,也就是先发0x03,再发0xe8,这16位在发送主机的缓冲区中也应该是低地址存0x03,高地址存0xe8,但是如果发送主机是小端字节序的,这16位被解释成0xe803,而不是1000。因此发送主机把1000填到发送缓冲区之前需要做字节序的转换。同样的,接收主机如果是小端字节序的,接到16位的源端口号也要做字节序的转换,如果主机是大端字节序的,发送和接收都不需要做转换。同理,32位的IP地址也要考虑网络字节序和主机字节序的问题。

为了使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

1
2
3
4
5
6
#include<arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint32_t htons(uint32_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint32_t ntohs(uint32_t netshort);

其中h标识host,n表示network,l表示32位长整数,s表示16位短整数。如果主机时小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机时大端字节序,这些函数不做转换,将参数原封不动的返回。

IP地址转换函数
1
2
3
4
5
6
7
8
9
10
int inet_pton(int af,const char *src,void *dst);

参数
af:AF_INET、AF_INET6
src:传入,IP地址(点分十进制)
dst:传出,转换后的网络字节序的IP地址
返回
成功:1
异常:0 说明src指向的不是一个有效的IP地址
失败:-1

网络套接字函数

socket函数
1
2
3
4
5
6
7
8
9
10
#include<sys/socket.h>
int socket(int domain,int type,int protocol) 创建一个套接字

domain:AF_INET、AF_INET6、AF_UNIX
type:SOCK_STREAM、SOCK_DGRAM
protocol:0

返回值
成功:新套接字所对应的文件描述符
失败:-1 errno
bind函数

给socket绑定一个地质结构(IP+port)

1
2
3
4
5
6
7
8
9
10
11
12
13
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:socket函数返回值
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888)
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr:(struct sockaddr *)&addr
addrlen:sizeof(addr)地址结构的大小

返回值
成功:0
失败:-1 errno
listen函数

设置同时与服务器建立连接的上限数

1
2
3
4
5
6
7
8
int listen(int sockfd,int backlog)

sockfd:socket函数返回值
backlog:上限数值,最大128

返回值
成功:0
失败:-1 errno
accept函数

阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符。

1
2
3
4
5
6
7
8
9
10
int accept(int sockfd,struct socketaddr *addr,socklen_t *addrlen);

sockfd:socket函数返回值
addr:传出参数,成功与服务器建立连接的那个客户端的地址结构(IP+port)
socklen_t clit_addr_len = sizeof(addr);
addrlen:传入传出。入:addr的大小。出:客户端addr实际大小

返回值
成功:能与服务器进行数据通信的socket对应的文件描述符
失败:-1 errno
connect函数

使用现有的socket与服务器建立连接

1
2
3
4
5
6
7
8
9
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

sockfd:socket函数返回值
addr:传入参数。服务器的地址结构
addrlen:服务器的地址结构的大小

返回值
成功:0
失败:-1 errno
实现server端
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
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<errno.h>
#include<pthread.h>

#define SERV_PORT 9527

void sys_err(const char *str)
{
perror(str);
exit(1);
}

int main(int argc,char *argv[])
{
int lfd = 0,cfd = 0;
int ret;
int i;
char buf[BUFSIZ],client_IP[1024];
struct sockaddr_in serv_addr,clit_addr;
socklen_t clit_addr_len;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd == -1){
sys_err("socket error");
}

bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));

listen(lfd,128);

clit_addr_len = sizeof(clit_addr);
cfd = accept(lfd,(struct sockaddr *)&clit_addr,&clit_addr_len);
if(cfd == -1)
sys_err("accept error");
printf("client ip:%s port:%d\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),ntohs(clit_addr.sin_port));
while(1){
ret = read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
for (i=0;i<ret;i++)
buf[i] = toupper(buf[i]);
write(cfd,buf,ret);
}
close(lfd);
close(cfd);
return 0;
}
实现client端
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
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<errno.h>
#include<pthread.h>

#define SERV_PORT 9527

void sys_err(const char *str)
{
perror(str);
exit(1);
}

int main(int argc,char *argv[])
{
int cfd;
int conter = 10;
char buf[BUFSIZ];
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr.s_addr);
cfd = socket(AF_INET,SOCK_STREAM,0);
if(cfd == -1)
sys_err("socket error");
int ret = connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret != 0)
sys_err("connect err");

while(--conter){
write(cfd,"hello\n",6);
ret = read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
sleep(1);
}
close(cfd);
return 0;
}
实现多进程并发服务器

首先实现功能封装函数wrap.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
#include "wrap.h"
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<sys/socket.h>

void perr_err(const char *str)
{
perror(str);
exit(-1);
}

int Accept(int fd,struct sockaddr *sa,socklen_t *salenptr)
{
int n;

again:
if((n = accept(fd,sa,salenptr)) < 0){
if((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
int Bind(int fd,const struct sockaddr *sa,socklen_t salen)
{
int n;
n = connect(fd,sa,salen)
if(n<0){
perr_exit("connect error");
}
return n;
}

int Socket(int domain,int type,int protocol)
{
int n;
n = socket(domain,type,protocol);
if(n == -1){
sys_err("socket error");
return 0;
}
return n;

}

int Listen(int sockfd,int backlog)
{
int n = 0;
n = listen(sockfd,backlog);
if(n == -1){
sys_err("listen error");
return n;
}
return 0;
}

int Connect(int fd,const struct sockaddr *sa,socklen_t salen)
{
int n;
n = connect(fd,sa,salen);
if(n<0){
perr_exit("connect error");
}
return n;
}

ssize_t Read(int fd,void *ptr,size_t nbytes)
{
ssize_t n;
again:
if((n = read(fd,ptr,nbytes)) == -1){
if(errno == EINTR)
goto again;
else
return -1;
}
return n;
}

ssize_t Write(int fd,const void *ptr,size_t nbytes)
{
ssize_t n;
again:
if((n = write(fd,ptr,nbytes)) == -1){
if(errno == EINTR)
goto again;
else
return -1;
}
return n;
}

int Close(int fd)
{
int n;
if((n == close(fd)) == -1)
perr_exit("close error");
return n
}

ssize_t Readn(int fd,void *vptr,size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;

ptr = vptr;
nleft = n;

while(nleft > 0){
if((nread = read(fd,ptr,nleft)) < 0){
if(errno == EINTR)
nread = 0;
else
return -1;
} else if (nread == 0)
break
nleft-= nread;
ptr += nread;
}
return n-nleft;
}

ssize_t Writen(int fd,const void *vptr,size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;

ptr = vptr;
nleft = n;

while(nleft > 0){
if((nwritten = write(fd,ptr,nleft)) <= 0){
if(nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}

实现自定义头文件wrap.h,代码如下

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
#ifndef _WRAP_H_
#define _WRAP_H_
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<errno.h>
#include<pthread.h>

void perr_err(const char *str);
int Accept(int fd,struct sockaddr *sa,socklen_t *salenptr);
int Bind(int fd,const struct sockaddr *sa,socklen_t salen);
int Connect(int fd,const struct sockaddr *sa,socklen_t salen);
int Socket(int domain,int type,int protocol);
int Listen(int sockfd,int backlog);
ssize_t Read(int fd,void *ptr,size_t nbytes);
ssize_t Write(int fd,const void *ptr,size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd,void *vptr,size_t n);
ssize_t Writen(int fd,const void *vptr,size_t n);


#endif

实现服务端server.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
#include<stdio.h>
#include<ctype.h>
#include<signal.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<errno.h>
#include<pthread.h>
#include "wrap.h"

#define SRV_PORT 9527

void catch_child(int signum)
{
while(waitpid(0,NULL,WNOHANG) > 0);
return ;
}

int main(int argc,char *argv[])
{
int lfd,cfd;
pid_t pid;
struct sockaddr_in srv_addr,clt_addr;
socklen_t clt_addr_len;
char buf[BUFSIZ];
int ret,i;
memset(&srv_addr,0,sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

lfd = Socket(AF_INET,SOCK_STREAM,0);

Bind(lfd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));

Listen(lfd,128);

clt_addr_len = sizeof(clt_addr);

while(1){

cfd = Accept(lfd,(struct sockaddr *)&clt_addr,&clt_addr_len);
pid = fork();
if(pid < 0){
perr_err("fork error");
} else if (pid == 0) {
close(lfd);
break;
} else {
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD,&act,NULL);
if(ret != 0)
perr_err("sigaction error");
close(cfd);
continue;
}
}
if(pid == 0){
for(;;){
ret = Read(cfd,buf,sizeof(buf));
if(ret == 0){
close(cfd);
exit(1);
}
for(i = 0;i<ret;i++)
buf[i] = toupper(buf[i]);
write(cfd,buf,ret);
write(STDOUT_FILENO,buf,ret);
}
}
return 0;
}

联合编译生成server文件

1
gcc server.c wrap.c -o server

多路IO转接

select

借助内核,select来监听客户端连接、数据通信事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

nfds:监听的所有文件描述符中,最大文件文件描述符+1
readfds:读文件描述符监听集合
writefds:写文件描述符监听集合
exceptfds:异常文件描述符监听集合
timeout:
>0 : 设置监听超时时长
NULL : 阻塞监听
0 : 非阻塞监听,轮询

返回值
>0 : 所有监听集合中,满足对应事件的总数
0 : 没有满足监听条件的文件描述符
-1 : errno
1
2
3
4
5
6
7
8
9
void FD_ZERO(fd_set *set) //清空一个文件描述符集合
fd_set rset;
FD_ZERO(&rset);
void FD_SET(int fd,fd_set *set) //将待监听的文件描述符,添加到监听集合中
FD_SET(3,&rset) FD_SET(5,&rset) FD_SET(6,&rset)
void FD_CLR(int fd,fd_set *set) //将一个文件描述符从监听集合中移除
FD_CLR(4,&rset);
void FD_ISSET(int fd,fd_set *set) //判断一个文件描述符是否在监听集合中
FD_ISSET(4,&rset);
使用select实现多路IO转接

server端代码如下

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
#include<stdio.h>
#include<ctype.h>
#include<signal.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<errno.h>
#include<pthread.h>
#include "wrap.h"

#define SRV_PORT 9527

int main(int argc,char *argv[])
{
int listenfd,connfd;
struct sockaddr_in srv_addr,clt_addr;
socklen_t clt_addr_len;
char buf[BUFSIZ];
int opt = 1;
memset(&srv_addr,0,sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

listenfd = Socket(AF_INET,SOCK_STREAM,0);

Bind(listenfd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));

Listen(listenfd,128);

fd_set rset,allset; //定义 读集合,备份集合allset
FD_ZERO(&allset); //清空监听集合
FD_SET(listenfd,&allset); //将待监听fd添加到监听集合中

int ret,maxfd = 0,i,n,j;
maxfd = listenfd; //最大文件描述符
while(1){
rset = allset; //备份
ret = select(maxfd+1,&rset,NULL,NULL,NULL); //使用select监听
if(ret < 0)
perr_err("select error");
if(FD_ISSET(listenfd,&rset)){ //listenfd满足监听的读事件
clt_addr_len = sizeof(clt_addr);
connfd = Accept(listenfd,(struct sockaddr*)&clt_addr,&clt_addr_len);
FD_SET(connfd,&allset); //将新产生的fd添加到监听集合中,监听数据读事件
if(maxfd < connfd) //修改maxfd
maxfd = connfd;
if(ret == 1) //说明select只返回一个,并且是listenfd,后续指令无需执行
continue;
}
for(i=listenfd+1;i<=maxfd;i++){ //处理满足读事件的fd
if(FD_ISSET(i,&rset)){ //找到满足读事件的那个fd
n = read(i,buf,sizeof(buf));
if(n == 0){ //检测到客户端已经关闭连接
Close(i);
FD_CLR(i,&allset); //将关闭的fd移除出监听集合
} else if(n == -1){
perr_err("read error");
}
for(j = 0;j<n;j++)
buf[j] = toupper(buf[j]);
write(i,buf,n);
write(STDOUT_FILENO,buf,n);
}
}

}

Close(listenfd);

return 0;
}

自定义文件头wrap.h和封装函数wrap.c代码和之前的相同。

poll
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int poll(struct pollfd *fds, nfds_t nfds,int timeout)

fds: 监听的文件描述符数组
struct pollfd{
int fd: 待监听的文件描述符
short events: 待监听的文件描述符对应的监听事件(POLLIN、POLLOUT、POLLERR)
short revents: 传入时,给0。如果满足对应事件的话,返回非0(POLLIN、POLLOUT、POLLERR)
}
nfds: 监听数组的,实际有效监听个数
timeout: 超时时长
>0: 设置监听超时时长
-1: 阻塞监听
0: 非阻塞监听,轮询
返回值
>0 : 所有监听集合中,满足对应事件的总数
0 : 没有满足监听条件的文件描述符
-1 : errno

read函数返回值

1
2
3
4
5
>0: 实际读到的字节数
=0: socket中,表示对端关闭。close()
-1: 如果errno == EINTR 被异常中断,需要重启
如果errno == EAGAIN或EWOULDBLOCK 以非阻塞方式读数据,但是没有数据。需要再次读
如果errno == ECONNRESET 说明连接被重置,需要close()。移除监听队列

poll的优点:

  • 自带数组结构。可以将监听事件集合和返回事件集合分离

  • 扩展 监听上限,超出1024限制

缺点

  • 不能跨平台。只能在Linux下使用
  • 无法直接定位满足监听事件的文件描述符,编码难度较大
epoll
1
2
3
int epoll_create(int size)
size: 创建的红黑树的监听节点数量(仅供内核参考)
返回值: 指向新创建的红黑树的根节点的fd。失败返回-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
epfd: epoll_create函数的返回值 epfd
op: 对该监听红黑树所做的操作
EPOLL_CTL_ADD 添加fd到 监听红黑树
EPOLL_CTL_MOD 修改fd在 监听红黑树上的监听事件
EPOLL_CTL_DEL 将一个fd从 监听红黑树上摘下(取消监听)
fd: 待监听的fd
event: 本质 struct epoll_event 结构体
events:
EPOLLIN、EPOLLOUT、EPOLLERR
data: 联合体
int fd: 对应监听事件的fd
void *ptr
uint32_t u32
uint64_t u64
返回值: 成功0 失败返回-1 设置errno
1
2
3
4
5
6
7
8
9
10
11
12
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)
epfd: epoll_create函数的返回值 epfd
events: 传出参数,数组,满足监听条件的fd结构体
maxevents: 数组元素的总个数
timeout:
>0: 设置监听超时时长(毫秒)
-1: 阻塞监听
0: 非阻塞监听,轮询
返回值:
>0: 满足监听的总个数,可以用作循环上限
0: 没有fd满足监听事件
-1: 失败。errno
事件模型

EPOLL事件有两种模型

  • Edge Triggered(ET):边缘触发只有数据到来才触发,不管缓存区中是否还有数据

    1
    2
    struct epoll_event event;
    event.events = EPOLLIN | EPOLLET;
  • Level Triggered(LT):水平触发只要有数据都会触发

epoll反应堆模型

原来

1
socket、bind、listen -- epoll_create创建监听红黑树 -- 返回epfd -- epoll_ctl()向树上添加一个监听fd -- while(1) -- epoll_wait监听 -- 对应监听fd有事件产生 -- 返回监听满足数组 -- 判断返回数组元素 -- lfd满足 -- Accept -- cfd满足 -- read() -- 小写转大写 -- write回去

反应堆

1
socket、bind、listen -- epoll_create创建监听红黑树 -- 返回epfd -- epoll_ctl()向树上添加一个监听fd -- while(1) -- epoll_wait监听 -- 对应监听fd有事件产生 -- 返回监听满足数组 -- 判断返回数组元素 -- lfd满足 -- Accept -- cfd满足 -- read() -- 小写转大写 -- cfd从监听红黑树上摘下 -- EPOLLOUT -- 回调函数 -- epoll_ctl() -- PEOLL_CTL_ADD 重新放到红黑树上监听写事件 -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到红黑树上监听读事件 -- epoll_wait监听

UDP并发服务器

server

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
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<errno.h>
#include<pthread.h>

#define SERV_PORT 9527

void sys_err(const char *str)
{
perror(str);
exit(1);
}

int main(int argc,char *argv[])
{
int lfd = 0;
int ret;
int i;
char buf[BUFSIZ];
char str[INET_ADDRSTRLEN];
struct sockaddr_in serv_addr,clit_addr;
socklen_t clit_addr_len;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = socket(AF_INET,SOCK_DGRAM,0); //将tcp流式协议改为报式协议
if(lfd == -1){
sys_err("socket error");
}

bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
printf("Accepting connections ... \n");

while(1){
clit_addr_len = sizeof(clit_addr);
ret = recvfrom(lfd,buf,BUFSIZ,0,(struct sockaddr *)&clit_addr,&clit_addr_len);
if(ret == -1)
sys_err("recvfrom eror");
printf("received from %s at PORT %d\n",inet_ntop(AF_INET,&clit_addr.sin_addr,str,sizeof(str)),ntohs(clit_addr.sin_port));
for (i=0;i<ret;i++)
buf[i] = toupper(buf[i]);
ret=sendto(lfd,buf,ret,0,(struct sockaddr *)&clit_addr,clit_addr_len);
if(ret == -1)
sys_err("sendto error");
}
close(lfd);
return 0;
}

client

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
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<errno.h>
#include<pthread.h>

#define SERV_PORT 9527

void sys_err(const char *str)
{
perror(str);
exit(1);
}

int main(int argc,char *argv[])
{
int sockfd,n;
char buf[BUFSIZ];
struct sockaddr_in servaddr;
sockfd = socket(AF_INET,SOCK_DGRAM,0);

bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
//bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));

while(fgets(buf,BUFSIZ,stdin) != NULL){
n = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&servaddr,sizeof(servaddr));
if(n == -1)
sys_err("sendto error");
n = recvfrom(sockfd,buf,BUFSIZ,0,NULL,0);
if(n == -1)
sys_err("recvfrom error");
write(STDOUT_FILENO,buf,n);
}
close(sockfd);
return 0;
}