服务器端
因为UDP是面向数据报的,所以不能直接使用read(),write()来读写数据。
而是要使用recvfrom(),sendto()来读写数据
1.recvfrom()经socket接收数据
#include<sys/types.h>
#include<sys/socket.h>
//函数原型
ssize_t recvform(int sockfd,void *buf,size_t len, int flags,struct socket *src_addr, socklen_t *addrlen);
ssize_t 相当于 int ,socklen_t 相当于 int
参数说明:
socket:套接字
buf:缓冲区
len:缓冲区的长度
flags:调用操作方式,是以下一个或多个标志的组合,可通过or操作连接在一起,默认为0
src_addr:指针,指向装有源地址的缓冲区
addrlen:指针,指向from缓冲区长度值
返回值:
如果成功返回读取的字节数,如果失败,返回-1
如果远端关闭了文件描述符,返回0;
2.sendto()向指定目的地发送数据
#include<sys/types.h>
#include<sys/socket.h>
//函数原型
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,struct sockaddr *dest_addr, socklen_t addrlen);
参数说明:
socket:套接字
buff:待发送数据的缓冲区
size:缓冲区长度
flags:调用方式标志位,一般为0
dest_addr: 指针,指向目的套接字的地址
addrlen:所指地址的长度
返回值:成功返回发送的字节数,失败返回-1
地址转换函数:
这里只介绍基于IPV4的socket编程,我们通常用点分十进制(字符串)表示IP地址,但是在socket编程中,sockaddr_in中的IP地址字段是一个32位的IP
字符串转in_addr的函数:
#include<arpa/inet.h>
//点分十进制字符串转in_addr
int inet_aton(const char *strptr,struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
int inet_pton(int family,const char *strptr,void *addrptr);
in_addr转字符串的函数:
char* inet_ntoa(struct in_addr inaddr);
const char* inet_ntop(int family,const void *addrptr,char *strptr,size_t len);
注:inet_ntoa函数返回了一个char*,很显然函数内部自己开辟了一段内存来保存这个IP的结果,但是这个返回的结果在静态区,并不需要手动释放。
但是在多线程调用inet_ntoa,是否会出现异常?
- 在APUE中,明确提出inet_ntoa不是线程安全的函数
- 但是在centos7上测试,并没有出现问题,可能是内部实现加了互斥锁
- 在多线程环境下,推荐使用inet_ntop,这个函数由调研者提供一个缓冲区保存结果,可以规避线程安全问题
server.c
服务器应该做的事:
1.初始化服务器:
1)创建socket文件
2)将socket文件绑定到指定的结构体sockaddr中
2.开始事务循环处理
1)接收来自客户端的请求
2)处理请求,并将处理好的结构返回给客户端
(此处实现简单的回显式服务器-客户端,所以直接将请求打印到显示器)
//server.c 127.0.0.1 9090
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
int mian(int argc,char* argv*[])
{
if(argc!=3)
{
printf("Usage:./server[ip][port]\n");
return 1;
}
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
peeror("socket error\n");
return 2;
}
struct socketaddr_in local_addr;
local_addr.sin_famliy=AF_INET;
local_addr.sin_port = htons(atoi(argv[2]));
local_addr.sin_addr.s_addr = inet_addr(argv[1]);
int ret = bind(sockfd,(struct sockaddr*)&local_addr,sizeof(local_addr));
if(ret<0)
{
perror("bind\n");
return 1;
}
char buf[1024] ={0};
struct sockaddr_in peer;
while(1)
{
int len = sizeof(peer);
ssize_t read_size = recvfrom(sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
if(read_size<0)
{
continue;
}
else if(read_size>0)
{
buf[read_size]='\0';
printf("[%s:%d]:%s\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&peer,sizeof(peer));
}
}
return 0;
}
client.c
client要做的事:
1.检查命令行参数
2.创建socket文件,创建一个对端sockaddr结构体,作为发送函数的参数
3.发送请求给服务器
4.接受服务器的响应并打印出来