sockaddr结构:
struct sockaddr{
_SOCKADDR_COMMON(sa_); //Common data:address family and length.
char sa_data[14];//Address data.
};
sockaddr_in结构:
//Structure describing an Internet socket address.
struct sockaddr_in{
_SOCKADDR_COMMON(sin_);
in_port_t sin_port; //Port number.
struct in_addr sin_addr; //Internet address.
//Pad to size of 'struct sockaddr'.
unsigned char sin_zero[sizeof(struct sockaddr) - _SOCKADDR_COMMON_SIZE-sizeof(in_port_t) - sizeof(struct in_addr)];
};
虽然socket api的接⼝是sockaddr,但是我们真正在基于 IPv4编程时,使⽤的数据结构是sockaddr_in; 这个结构⾥主要有三部分信息 :地址类型,端⼝号, IP地址。
in_addr结构:
//Internet address.
typedef unit32_t in_addr_t;
struct in_addr{
in_addr_t s_addr;
};
in_addr⽤来表⽰⼀个IPv4的IP地址.其实就是⼀个32位的整数。
UDP服务器
client.c
//UDP版本的客户端
//1.从标准输入,读入一个字符
//2.把这个字符串通过socket发送给服务器
//3.尝试从服务器读取响应数据
//4.把响应结果写到标准输出上。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//通过命令行参数,指定客户端向哪个服务器发送请求。
//./client [ip] [port]
int main(int argc, char* argv[]){
if(argc != 3){
printf("Usage ./client [ip] [port]\n");
return 1;
}
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0){
perror("socket");
return 1;
}
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
while(1){
//stdin,stdout,stderr
//1.从标准输入读数据
printf("> ");
fflush(stdout);
char buf[1024] = {0};
ssize_t read_size = read(0,buf,sizeof(buf) - 1);
if(read_size < 0){
perror("read");
goto END;
}
if(read_size == 0){
printf("read done\n");
goto END;
}
buf[read_size] = '\0';
//2.把数据发送给服务器
sendto(sock,buf,strlen(buf),0,(sockaddr*)&server_addr,sizeof(server_addr));
//3.尝试从服务器读取数据,此时recvfrom不需要知道
//对端的ip和端口号,因为收到的数据一定是服务器发回来的。
//而服务器对应的ip 端口号,已知。
char buf_output[1024] = {0};
read_size = recvfrom(sock,buf_output,sizeof(buf_output) - 1,0,NULL,NULL);
//6.把读取到的结果写到标准输出上
printf("server resp %s\n",buf_output);
}
END:
close(sock);
return 0;
}
server.c
//UDP服务端
//1.尝试从socket中读取客户端发送的请求(字符串)
//2.读到请求之后,根据请求内容,计算生成响应(字符串)
//3.把响应写回到socket之中,在传递给客户端
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//服务器可执行程序 ip地址 端口号
//环回ip
//端口号最好大于1024,1-1023
//并且和当前主机上其他进程使用的端口号不重复
//./server 127.0.0.1 9090
int main (int argc,char* argv[]){
if(argc != 3){
printf("Usage ./server [ip] [port]\n");
return 1;
}
//AF_INET 表示ipv4版本的协议
//SOCK_DGRAM表示创建的socket要使用面向数据报的方式传输数据
//创建套接字
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0){
return 1;
}
//命名套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
//inet_addr,完成了把一个点分十进制的字符串ip地址转换成网络字节序的数字ip地址
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
//绑定端口号
int ret = bind(sock,(sockaddr*)&addr,sizeof(addr));
if(ret < 0){
perror("bind");
return 1;
}
//服务器接收客户信息
while(1){
sockaddr_in peer;
socklen_t len = sizeof(peer);
char buf[1024] = {0}; //缓冲区
ssize_t read_size = recvfrom(sock,buf,sizeof(buf) - 1,0,(sockaddr*)&peer,&len);
if(read_size < 0){
perror("recvfrom");
//接受失败之后,如何和处理这个错误合适?
continue;
}
buf[read_size] = '\0';
printf("[%s:%d] %s\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);
//对于一个正常的服务器来说,收到了用户的请求之后
//就要根据用户请求进行计算,得到结果响应给客户端。
//由于此处写的是一个回显服务器,此时没有计算的过程
//直接把结果写给客户端。
sendto(sock,buf,strlen(buf),0,(sockaddr*)&peer,sizeof(peer));
}
//关闭文件描述符
close(sock);
return 0;
}
Makefile
.PHONY:all
all:server client
server:server.c
gcc $^ -o $@
client:client.c
gcc $^ -o $@
.PHONY:clean
clean:
rm server client
运行结果: