本文借鉴【Linux】网络编程套接字——UDP协议。
sockaddr结构体:
socket API是一层抽象的网络编程接口,适用于底层各种网络协议,IPv4、IPv6等,而每一种网络协议的地址格式并不相同。
IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址。IPv4、IPv6的地址类型为AF_INET、AF_INET6,这样只要得到某种sockaddr结构体的首地址,不需要知道是具体哪种类型的sockade结构一条,就可以根据地址类型字段确定结构体中的内容。
socket API中的参数都是struct sockaddr*类型,在使用时需要强制类型转换成sockaddr_in;这样的好处是程序的通用性。
函数原型:int socket(int domain, int type, int protocol);
在参数表中,domain指定使用何种的地址类型,比较常用的有:
PF_INET, AF_INET: Ipv4网络协议;
PF_INET6, AF_INET6: Ipv6网络协议。
type参数的作用是设置通信的协议类型,可能的取值如下所示:
SOCK_STREAM: 提供面向连接的稳定数据传输,即TCP协议。
OOB: 在所有数据传送前必须使用connect()来建立连接状态。
SOCK_DGRAM: 使用不连续不可靠的数据包连接,即UDP协议。
SOCK_SEQPACKET: 提供连续可靠的数据包连接。
SOCK_RAW: 提供原始网络协议存取。
SOCK_RDM: 提供可靠的数据包连接。
SOCK_PACKET: 与网络驱动程序直接通信。
参数protocol用来指定socket所使用的传输协议编号。这一参数通常不具体设置,一般设置为0即可
socket编程接口:(创建通信端点,并返回描述符)
创建 socket ⽂件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
参数:domain表示域,当前要使用的协议簇,type(类型)表示使用哪种服务,protocol(协议)设置为0
返回值:调用成功返回文件描述符,失败返回-1
bind (绑定名称到套接字)
int bind(in sockfd, const struct sockaddr *addr(地址), socklen_t addrlen(地址长度))
recvfrom(从套接字接收数据)
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, \
struct sockaddr* src_addr, socklen_t* addrlen);
sendto(在套接字中发送信息)
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, \
struct sockaddr* src_addr, socklen_t* addrlen);
UDP通信:无连接的数据报传输协议。
特点:不可靠,不稳定,容易丢包,但传输速度快。(支持一对一,一对多,多对一和多对多的交互通信)
应用场合:周期性状态信息,图片和视频数据传输;区域网内数据传输。
这段程序是这样的:从客户端上sendto(发送)一条消息到服务器端,然后服务器端将收到的消息回显到客户端。
客户端:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("error: 需要 %s port端口 ip地址 \n", argv[0]);
return 1;
}
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock < 0)
{
perror("socket error");
return 2;
}
struct sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_port = htons(atoi(argv[1]));//prot端口例:8080 端口号是一个2字节16位的整数
peer.sin_addr.s_addr = inet_addr(argv[2]);//ip地址例:127.0.0.1
if(bind(sock, (struct sockaddr*)&peer, sizeof(peer)) < 0)
{
perror("bind");
return 3;
}
char buf[1024];
struct sockaddr_in server;
while(1)
{
socklen_t len = sizeof(server);
printf("Please Enter# ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf)-1);
if(s > 0)
{
buf[s-1] = 0;
sendto(sock, buf, sizeof(buf), 0, (struct sockaddr*)&peer, sizeof(peer));
ssize_t _s = recvfrom(sock, buf, sizeof(buf)-1, 0, (struct sockaddr*)&server, &len);
if(_s > 0)
{
buf[_s] = 0;
printf("server echo: %s\n", buf);
}
}
}
return 0;
}
服务端:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<arpa/inet.h>
int createsock(int port, const char* ip)
{
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock < 0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);//prot端口例:8080 端口号是一个2字节16位的整数
server.sin_addr.s_addr = inet_addr(ip);//127.0.0.1
if(bind(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
{
perror("bind");
return 3;
}
return sock;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("error:需要%s port端口 ip地址\n", argv[0]);
return 1;
}
int sock = createsock(atoi(argv[1]), argv[2]);
char buf[1024];
struct sockaddr_in local;
while(1)
{
socklen_t len = sizeof(local);
ssize_t s = recvfrom(sock, buf, sizeof(buf)-1, 0, (struct sockaddr*)&local, &len);
if(s > 0)
{
buf[s] = 0;
printf("[%s:%d]: %s\n", inet_ntoa(local.sin_addr), ntohs(local.sin_port), buf);
sendto(sock, buf, sizeof(buf), 0, (struct sockaddr*)&local, len);
}
}
return 0;
}
.PHONY:all //伪目标 .PHONY
all:client server
$@//表示规则中目标
$^//规则中的所有依赖条件
client:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:
clean:
rm -f client server
/* 当工程中的文件名和makefile中的目标重名时,就会有伪目标。
执行make命令时会发现提示目标文件已经是最新的了,将不被不执行!
如果我想让makefile中某个命令永远被执行。
可以在makefile目标前加上.PHONY:'目标名'*/