讨论:跨主机的传输要注意的问题
- 字节序问题
大端:低地址放高字节
小端:低地址放低字节
主机字节序:host
网络字节序:network
解决_ to __: htons, htonl, ntohs, ntohl
- 对齐
逻辑地址号(0开始)能否整除sizeof(类型)
eg:
struct {
int i;
float f;
char c;
}
解决:不对齐!
- 类型长度问题:
int
char
解决:int32_t, uint32_t, int64_t, int8_t
1.SOCKET是什么
RETURN VALUE
On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropriately.
参数domain:
参数type:
SOCK_STREAM流式,SOCK_DGRAM报式
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:
AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
AF_INET6 与上面类似,不过是来用IPv6的地址
AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type:
SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol:
传0 表示使用默认协议。
返回值:
成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno
2.报式套接字
被动端:(先运行)
1.取得SOCKET
2.给SOCKET取得地址
3.收/发消息
4.关闭SOCKET
主动端:
1.取得SOCKET
2.给SOCKET取得地址(可省略)
3.发/收消息
4.关闭SOCKET
函数:
const struct sockaddr * addr不同domain不同(The rules used in name binding vary between address families. Consult the manual entries in Section 7 for detailed information. For AF_INET, see ip(7); for AF_INET6, see ipv6(7); for AF_UNIX, see unix(7); for AF_APPLETALK, see ddp(7); for AF_PACKET, see packet(7); for AF_X25, see x25(7); and for AF_NETLINK, see netlink(7).)
inet_pton
描述:
This function converts the character string src into a network address structure in the af address family, then copies the network address structure to dst. The af argument must be either AF_INET or AF_INET6. dst is written in network byte order.
recv用于流式传输(点对点一对一的,不需要知道对端地址)
recvfrom用于报式
send用于流式,sendto用于报式,原因同recv和recvfrom
proto.h
#ifndef PROTO_H__
#define PROTO_H__
#define RCVPORT "1989" //端口
#define NAMESIZE 11
#include<stdint.h>
struct msg_st
{
uint8_t name[NAMESIZE];
uint32_t math;
uint32_t chinese;
}__attribute__((packed));//不要对齐
#endif
rcver.c
#include<stdio.h>
#include<stdlib.h>
#include"proto.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#define IPSTRSIZE 40
int main() {
int sd;
struct sockaddr_in laddr, raddr; // man 7 ip看 Address format
struct msg_st rbuf; //消息结构体
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
// 1.取得SOCKET
sd = socket(AF_INET, SOCK_DGRAM, 0/*IPPROTO_UDP*/); // 报式
if(sd < 0) {
perror("socket()");
exit(1);
}
// 2.给SOCKET取得地址
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);//0.0.0.0可以匹配任何地址(被换成自己的地址)
if(bind(sd,(void*)&laddr,sizeof(laddr)) < 0) {
perror("bind()");
exit(1);
}
raddr_len = sizeof(raddr);
// 3.收/发消息
raddr_len = 0;//一定要初始化
while(1) {
recvfrom(sd, &rbuf, sizeof(rbuf),0, (void*)&raddr, &raddr_len);
//This function converts the network address structure src in the af address family into a character string.
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
printf("---MESSAGE FROM %s:%d---\n", ipstr, ntohs(raddr.sin_port));
printf("NAME = %s\n", rbuf.name);
printf("MATH = %d\n", ntohl(rbuf.math));
printf("CHINESE = %d\n", ntohl(rbuf.chinese));
}
// 4.关闭SOCKET
close(sd);
exit(0);
}
snder.c
#include<stdio.h>
#include<stdlib.h>
#include"proto.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char**argv) {
int sd;
struct msg_st sbuf;
struct sockaddr_in raddr;
if(argc < 2) {
fprintf(stderr, "Usage...\n");
exit(1);
}
// 1.取得SOCKET
sd = socket(AF_INET, SOCK_DGRAM, 0);
if(sd < 0) {
perror("socket()");
exit(0);
}
// 2.给SOCKET取得地址(可省略)
// 3.发/收消息
strcpy(sbuf.name, "Alan");
sbuf.math = htonl(rand() % 100);
sbuf.chinese = htonl(rand() % 100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, argv[1], &raddr.sin_addr);
if(sendto(sd, &sbuf, sizeof(sbuf), 0, (void*)&raddr, sizeof(raddr)) < 0) {
perror("sendto()");
exit(1);
}
puts("OK!");
// 4.关闭SOCKET
close(sd);
exit(0);
}
结果:
3.动态报式套接字
名字改成不定长的
proto.h
#ifndef PROTO_H__
#define PROTO_H__
#define RCVPORT "1989" //端口
//name能存在的最大长度
#define NAMEMAX (512-8-8) //512是udp包的推荐长度,第一个8是udp包的报头大小,第二个8是定长uint32_t math and chinese
#include<stdint.h>
struct msg_st
{
uint32_t math;
uint32_t chinese;
uint8_t name[1]; //只是个占位符
}__attribute__((packed));//不要对齐
#endif
rcver.c
#include<stdio.h>
#include<stdlib.h>
#include"proto.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#define IPSTRSIZE 40
int main() {
int sd, size;
struct sockaddr_in laddr, raddr; // man 7 ip看 Address format
struct msg_st* rbufp; //消息结构体
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
size = sizeof(struct msg_st) + NAMEMAX - 1;
rbufp = malloc(size);
if(rbufp == NULL) {
perror("malloc");
exit(1);
}
// 1.取得SOCKET
sd = socket(AF_INET, SOCK_DGRAM, 0/*IPPROTO_UDP*/); // 报式
if(sd < 0) {
perror("socket()");
exit(1);
}
// 2.给SOCKET取得地址
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);//0.0.0.0可以匹配任何地址(被换成自己的地址)
if(bind(sd,(void*)&laddr,sizeof(laddr)) < 0) {
perror("bind()");
exit(1);
}
raddr_len = sizeof(raddr);
// 3.收/发消息
raddr_len = 0;//一定要初始化
while(1) {
recvfrom(sd, rbufp, size,0, (void*)&raddr, &raddr_len);
//This function converts the network address structure src in the af address family into a character string.
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
printf("---MESSAGE FROM %s:%d---\n", ipstr, ntohs(raddr.sin_port));
printf("NAME = %s\n", rbufp->name);
printf("MATH = %d\n", ntohl(rbufp->math));
printf("CHINESE = %d\n", ntohl(rbufp->chinese));
}
// 4.关闭SOCKET
close(sd);
exit(0);
}
snder.c
#include<stdio.h>
#include<stdlib.h>
#include"proto.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char**argv) {
int sd, size;
struct msg_st* sbufp;
struct sockaddr_in raddr;
if(argc < 3) {
fprintf(stderr, "Usage...\n");
exit(1);
}
if(strlen(argv[2]) > NAMEMAX) {
fprintf(stderr, "Name is too long!\n");
exit(1);
}
size = sizeof(struct msg_st) + strlen(argv[2]);
sbufp = malloc(size);
if (sbufp == NULL) {
perror("malloc");
exit(1);
}
// 1.取得SOCKET
sd = socket(AF_INET, SOCK_DGRAM, 0);
if(sd < 0) {
perror("socket()");
exit(0);
}
// 2.给SOCKET取得地址(可省略)
// 3.发/收消息
strcpy(sbufp->name, argv[2]);
sbufp->math = htonl(rand() % 100);
sbufp->chinese = htonl(rand() % 100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, argv[1], &raddr.sin_addr);
if(sendto(sd, sbufp, size, 0, (void*)&raddr, sizeof(raddr)) < 0) {
perror("sendto()");
exit(1);
}
puts("OK!");
// 4.关闭SOCKET
close(sd);
exit(0);
}
4. 报式套接——多点通讯
多点通讯只能用报式实现,流式是点对点的。
多点通讯:
广播(全网广播,子网广播)
多播/组播
函数:
获取和设置socket属性
广播
proto.h
#ifndef PROTO_H__
#define PROTO_H__
#define RCVPORT "1989" //端口
//name能存在的最大长度
#define NAMEMAX (512-8-8) //512是udp包的推荐长度,第一个8是udp包的报头大小,第二个8是定长uint32_t math and chinese
#include<stdint.h>
struct msg_st
{
uint32_t math;
uint32_t chinese;
uint8_t name[1]; //只是个占位符
}__attribute__((packed));//不要对齐
#endif
rcver.c
#include<stdio.h>
#include<stdlib.h>
#include"proto.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#define IPSTRSIZE 40
int main() {
int sd, size;
struct sockaddr_in laddr, raddr; // man 7 ip看 Address format
struct msg_st* rbufp; //消息结构体
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
size = sizeof(struct msg_st) + NAMEMAX - 1;
rbufp = malloc(size);
if(rbufp == NULL) {
perror("malloc");
exit(1);
}
// 1.取得SOCKET
sd = socket(AF_INET, SOCK_DGRAM, 0/*IPPROTO_UDP*/); // 报式
if(sd < 0) {
perror("socket()");
exit(1);
}
int val = 1;
if(setsockopt(sd, SOL_SOCKET, SO_BROADCAST ,&val, sizeof(val)) < 0) {
perror("setsockopt()");
exit(1);
}
// 2.给SOCKET取得地址
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);//0.0.0.0可以匹配任何地址(被换成自己的地址)
if(bind(sd,(void*)&laddr,sizeof(laddr)) < 0) {
perror("bind()");
exit(1);
}
raddr_len = sizeof(raddr);
// 3.收/发消息
raddr_len = 0;//一定要初始化
while(1) {
recvfrom(sd, rbufp, size,0, (void*)&raddr, &raddr_len);
//This function converts the network address structure src in the af address family into a character string.
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
printf("---MESSAGE FROM %s:%d---\n", ipstr, ntohs(raddr.sin_port));
printf("NAME = %s\n", rbufp->name);
printf("MATH = %d\n", ntohl(rbufp->math));
printf("CHINESE = %d\n", ntohl(rbufp->chinese));
}
// 4.关闭SOCKET
close(sd);
exit(0);
}
snder.c
#include<stdio.h>
#include<stdlib.h>
#include"proto.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char**argv) {
int sd, size;
struct msg_st* sbufp;
struct sockaddr_in raddr;
// if(argc < 3) {
// fprintf(stderr, "Usage...\n");
// exit(1);
// }
if(strlen(argv[2]) > NAMEMAX) {
fprintf(stderr, "Name is too long!\n");
exit(1);
}
size = sizeof(struct msg_st) + strlen(argv[2]);
sbufp = malloc(size);
if (sbufp == NULL) {
perror("malloc");
exit(1);
}
// 1.取得SOCKET
sd = socket(AF_INET, SOCK_DGRAM, 0);
if(sd < 0) {
perror("socket()");
exit(1);
}
int val = 1;
if(setsockopt(sd, SOL_SOCKET, SO_BROADCAST, &val, sizeof(val)) < 0) {
perror("setsockopt()");
exit(1);
}
// 2.给SOCKET取得地址(可省略)
// 3.发/收消息
memset(sbufp, '\0' , sizeof(*sbufp));
strcpy(sbufp->name, argv[2]);
sbufp->math = htonl(rand() % 100);
sbufp->chinese = htonl(rand() % 100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, "255.255.255.255", &raddr.sin_addr); //全网广播
if(sendto(sd, sbufp, size, 0, (void*)&raddr, sizeof(raddr)) < 0) {
perror("sendto()");
exit(1);
}
puts("OK!");
// 4.关闭SOCKET
close(sd);
exit(0);
}