创建socket套接字的API
函数原型
参数说明
type为数据传输方式,常用的有SOCK_STREAM和SOCK_DGRAM。
前者表示面向有连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或者丢失则重新发送,但效率相对较慢。常见的http协议就是采用的SOCK_STREAM传输数据,因为要保证数据的正确性,否则网页就不能正确解析。
后者表示面向无连接的数据传输方式。计算机只管传输数据,不做数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。但是这种传输方式的效率比前者高。(一般的视频聊天和语音聊天就使用SOCK_DGRAM传输数据,SOCK_DGRAM也没有想象中那么糟糕,不会频繁的丢失数据,数据错误只是小概率事件。)
protocol表示传输协议,常用的有IPPROTO_TCP和IPPTOTO_UDP,分别代表TCP传输协议和UDP传输协议。一般有了af和type两个参数就足够了,因为操作系统会自动推演出协议类型,除非:有两种不同的协议支持同一种地址类型和数据传输类型。如果不是这种特殊情况,那么这个参数就可以填 0。
绑定端口号的API
函数原型
参数说明
这个函数的第二个参数在传参的时候,我们通常是定义一个sockaddr_in类型的结构体,初始化后,强制类型转换为sockaddr类型的。
sockaddr_in结构体成员说明:
struct sockaddr_in{
sa_family_t sin_family;
uin16_t sin_port;
struct in_addr sin_addr;
char buf[8];
}
成员说明:
1)sin_family和socket()的第一个参数含义相同,取值也要保持一致。
2)uint16_t 的长度为两个字节,理论上端口号的取值范围为0~65536,但是0~1023的端口一般由系统分配给特定的服务程序(称为知名端口号),例如Web服务的端口号为80,FTP服务的端口号为21,所以我们的程序使用的端口号一般要大于1023,小于65536。(端口号要用htos()函数转换,hots()函数的作用是将本机字节序转换为网络字节序)
3)sin_addr是struct in_addr结构体类型的变量。
struct in_addr{
in_addr_t s_addr;
}
in_addr_t在头文件< netinet/in.h >中定义,等价于unisgned long,长度为4个字节,也就是说,s_addr是一个整数,而IP地址是一个字符串,所以这个参数需要用inet_addr()函数进行转换,eg:
unsigned long ip = inet_addr("127.0.0.1");
4)sin_zero[8]是多余的8个字节,一般用memset函数将结构体变量全置为0,给前三个成员复制后,第四个成员自然是0。
接收数据并保存源地址的API
函数原型:
int recvfrom(socket s, char *buf, int len, int flags, \
struct sockaddr *from, int fromlen);
参数说明:
向指定目的地发送数据的API
函数原型:
int sendto(socket s, const char *buf, int len, int flags, \
const struct sockaddr *to, int tolen);
参数说明:
业务逻辑:
1. 尝试从socket中读取客户端发送的请求
2. 读取到请求后,根据请求内容,计算生成响应
3. 把响应写回到socket中,再传递给客户端
注意:
服务器端代码
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
int main(int argc, char *argv[])
{
if(argc != 3){
printf("Usage ./server [ip] [port]\n"_;
return 1;
}
//创建socket文件
int socket = socket(AF_INET,SOCK_DGRAM, 0);
if(socket < 0){
perror("socket");
return 1;
}
//绑定端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET;
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};
//从socket中接收消息
ssize 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;
}
客户端代码
#include
#include
#include
#include
#include
#include
#iclude
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//通过命令行参数,指定客户端向哪个服务器发送服务
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("sock");
return 1;
}
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argc[1]);
server_addr,sin_port = htons(atoi(argv[2]));
while(1){
//1.从标准输入读取数据
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if(read_size < 0){
perror("read");
close(socket);
return 1;
}
if(read_size == 0){
printf("read done!\n");
close(socket);
return 0;
}
buf[read_size] = '\0';
//2.把数据发送给服务器
sendto(socket, buf, strlen(buf), 0, \
(sockaddr*)&server_addr, sizeof(server_addr));
//3.尝试从服务器读取数据
//此时revcfrom不需要知道对端的IP和端口号
//因为收到的数据一定是服务器发回来的
//而服务器对应的IP端口号已知
char buf_output[1024] = {0};
read_size = recvfrom(sock, buf_output, \
sizeof(buf_output) - 1, 0, NULL, NULL);
//4.把读取到的结果写到标准输出上
printf("server resp %s\n",buf_output);
}
close(socket);
return 0;
}