1.0 地址 通用地址(sockaddr), IPV4地址sockaddr_in 2.0 端口 3.0大端模式(网络应用) 小端模式
网络字节序 <--->本机字节序 htons(),ntohs(),htonl()和ntohl().
网络字节序 <---->IP字符串
- int inet_aton(const char *strptr , struct in_addr *addrptr);
- in_addr_t inet_addr(const char *strptr);
- char *inet_ntoa (struct in_addr inaddr);
struct in_addr {
in_addr_t s_addr;
};
in_addr_t一般为32位的unsigned int,其字节顺序为网络字节序,即该无符号数采用大端字节序。其中每8位表示一个IP地址中的一个数值。
socket族 AF_INET
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);
int close(int fd);
int shutdown(int sockfd, int howto);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
1.1. 使用TCP协议的流程图
TCP通信的基本步骤如下:
服务端:socket---bind---listen---while(1){---accept---recv---send---close---}---close
客户端:socket----------------------------------connect---send---recv-----------------close
1.2. 使用UDP协议的流程图
UDP通信流程图如下:
服务端:socket---bind---recvfrom---sendto---close
客户端:socket----------sendto---recvfrom---close
服务器端
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#if 0
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// 创建用于通信的端口,返回描述符
// domain 包含以下域:
// Name Purpose Man page
// AF_UNIX, AF_LOCAL Local communication unix(7)
// AF_INET IPv4 Internet protocols ip(7)
// AF_INET6 IPv6 Internet protocols ipv6(7)
// AF_IPX IPX - Novell protocols
// AF_NETLINK Kernel user interface device netlink(7)
// AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
// AF_AX25 Amateur radio AX.25 protocol
// AF_ATMPVC Access to raw ATM PVCs
// AF_APPLETALK AppleTalk ddp(7)
// AF_PACKET Low level packet interface packet(7)
// AF_ALG Interface to kernel crypto API
// type 有以下选项:
// SOCK_STREAM 基于流的,适用于 TCP 套接字
// SOCK_DGRAM 基于数据报的,适用于 UDP 套接字
// SOCK_RAW 原始套接字
// protocol 子协议,如果没有子协议则为 0
// 成功返回套接字的描述符,失败返回 -1
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
// 绑定长度为 addrlen 的 addr 地址到 sockfd 套接字
// sockfd 套接字描述符
// addr 通用地址指针,传递具体地址时要进行强制类型转换
// addrlen 地址长度
// 成功返回 0,失败返回 -1
// 通用地址结构体
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// 只能用于 TCP
// 从 sockfd 套接字接收网络信息,保存到大小为 len 的缓冲区 buf 中
// flags 可取 0 或以下值按位或:
// MSG_DONTWAIT 非阻塞
// MSG_OOB 带外数据(紧急数据)
// MSG_PEEK 只提取数据而不从接收队列中删除它
// MSG_WAITALL 阻塞直到所有请求都满足
// recv() 等价于以下调用:
// recvfrom(fd, buf, len, flags, NULL, 0));
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
// 用于 UDP,但也可以用于 TCP
// 从 sockfd 套接字接收网络信息,保存到大小为 len 的缓冲区 buf 中
// 保存发送者的地址信息到大小为 addrlen 的地址 src_addr 中
// 注意,尽管 addrlen 是用来存放发送者的地址的大小的,但在有的系统
// 中必须提供一个正确的大小,才能正确接收网络数据
// 以上函数成功时返回收到的字节数,失败时返回 -1
// 返回 0 的情况:
// 1. 基于流的套接字(TCP),发送方已关闭,接收方会收到 0 个字节,
// 这相当于"文件尾"
// 2. 基于数据报的套接字(UDP)允许长度为 0 的数据报,当收到这样的
// 数据报,返回 0 个字节
// 3. 从流套接字收到 0 个字节时返回 0 字节
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// 只用于 TCP
// 将数据大小为 len 的 buf 中的数据通过 sockfd 发送
// flags 取值为 0 或以参下参数按位或:
// MSG_DONTWAIT 非阻塞
// MSG_NOSIGNAL 向已关闭的流套接字写不产生 SIGPIPE 信号
// MSG_OOB 带外数据(紧急数据)
// send() 等价于以下调用:
// sendto(sockfd, buf, len, flags, NULL, 0);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
// 既可以用于 UDP 也可以用于 TCP
// 将数据大小为 len 的 buf 中的数据通过 sockfd 发送到大小为 addrlen
// 的地址 dest_addr
// send() 和 sendto() 返回成功发送的字节数,或失败返回 -1
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
// 获取/设置套接字选项
// level 可取以下值:
// SOL_SOCKET 基本套接口
// IPPROTO_IP IPv4套接口
// IPPROTO_IPV6 IPv6套接口
// IPPROTO_TCP TCP套接口
// optname 可取以下值:
// SO_BROADCAST 广播
// SO_RCVBUF 接收缓冲区
// SO_REUSEADDR 地址可重用
// SO_REUSEPORT 端口可重用
// SO_SNDBUF 发送缓冲区
// 成功返回 0,失败返回 -1
#endif
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in svr_addr, clt_addr; //使用IPV4地址,但recvfrom与sendto 都是用通用地址
in_port_t port;
in_addr_t inaddr;
char *ip = "192.168.177.151";
int optval = 1; // 非 0 使能地址可重用
socklen_t socklen = sizeof clt_addr, optlen = sizeof optval;
char recv_buf[32] = "";
char send_buf[32] = "Hello client";
// 检查参数个数
if (argc != 3)
{
printf("用法:%s <本机 IP> <端口号>\n", argv[0]);
return -1;
}
ip = argv[1];
port = atoi(argv[2]);
// 创建 UDP 套接字
sockfd = socket(AF_INET /*PF_INET*/, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("创建套接字失败");
return -1;
}
// 设置套接字选项 - 地址可重用 //防止别的进程地址没有释放
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
&optval, optlen))
{
perror("设置地址可重用失败");
goto _out;
}
// 绑定本机地址
inaddr = inet_addr(ip);
if (INADDR_NONE == inaddr)
{
printf("%s 是非法 IP 地址\n", ip);
goto _out;
}
svr_addr.sin_family = AF_INET; // PF_INET
svr_addr.sin_port = htons(port); // 端口必须转换为网络字节序
//svr_addr.sin_addr.s_addr = inaddr; // 绑定到本机的某一个 IP
// 绑定到本机任意网络接口 可能本机有多个IP
svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (-1 == bind(sockfd, (struct sockaddr *) &svr_addr, sizeof svr_addr))
{
perror("绑定失败");
goto _out;
}
// 收
if (-1 == recvfrom(sockfd, recv_buf, sizeof recv_buf, 0,
(struct sockaddr *) &clt_addr, &socklen))
{
perror("接收失败");
goto _out;
}
printf("收到 %s(%d) 发来的消息:%s\n",
inet_ntoa(clt_addr.sin_addr),
ntohs(clt_addr.sin_port), recv_buf);
// 发
if (-1 == sendto(sockfd, send_buf, strlen(send_buf), 0,
(struct sockaddr *) &clt_addr, socklen))
{
perror("发送失败");
goto _out;
}
printf("发送成功\n");
_out:
// 关闭套接字
close(sockfd);
return 0;
}
客户端
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#if 0
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// 创建用于通信的端口,返回描述符
// domain 包含以下域:
// Name Purpose Man page
// AF_UNIX, AF_LOCAL Local communication unix(7)
// AF_INET IPv4 Internet protocols ip(7)
// AF_INET6 IPv6 Internet protocols ipv6(7)
// AF_IPX IPX - Novell protocols
// AF_NETLINK Kernel user interface device netlink(7)
// AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
// AF_AX25 Amateur radio AX.25 protocol
// AF_ATMPVC Access to raw ATM PVCs
// AF_APPLETALK AppleTalk ddp(7)
// AF_PACKET Low level packet interface packet(7)
// AF_ALG Interface to kernel crypto API
// type 有以下选项:
// SOCK_STREAM 基于流的,适用于 TCP 套接字
// SOCK_DGRAM 基于数据报的,适用于 UDP 套接字
// SOCK_RAW 原始套接字
// protocol 子协议,如果没有子协议则为 0
// 成功返回套接字的描述符,失败返回 -1
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
// 绑定长度为 addrlen 的 addr 地址到 sockfd 套接字
// sockfd 套接字描述符
// addr 通用地址指针,传递具体地址时要进行强制类型转换
// addrlen 地址长度
// 成功返回 0,失败返回 -1
// 通用地址结构体
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// 只能用于 TCP
// 从 sockfd 套接字接收网络信息,保存到大小为 len 的缓冲区 buf 中
// flags 可取 0 或以下值按位或:
// MSG_DONTWAIT 非阻塞
// MSG_OOB 带外数据(紧急数据)
// MSG_PEEK 只提取数据而不从接收队列中删除它
// MSG_WAITALL 阻塞直到所有请求都满足
// recv() 等价于以下调用:
// recvfrom(fd, buf, len, flags, NULL, 0));
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
// 用于 UDP,但也可以用于 TCP
// 从 sockfd 套接字接收网络信息,保存到大小为 len 的缓冲区 buf 中
// 保存发送者的地址信息到大小为 addrlen 的地址 src_addr 中
// 注意,尽管 addrlen 是用来存放发送者的地址的大小的,但在有的系统
// 中必须提供一个正确的大小,才能正确接收网络数据
// 以上函数成功时返回收到的字节数,失败时返回 -1
// 返回 0 的情况:
// 1. 基于流的套接字(TCP),发送方已关闭,接收方会收到 0 个字节,
// 这相当于"文件尾"
// 2. 基于数据报的套接字(UDP)允许长度为 0 的数据报,当收到这样的
// 数据报,返回 0 个字节
// 3. 从流套接字收到 0 个字节时返回 0 字节
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// 只用于 TCP
// 将数据大小为 len 的 buf 中的数据通过 sockfd 发送
// flags 取值为 0 或以参下参数按位或:
// MSG_DONTWAIT 非阻塞
// MSG_NOSIGNAL 向已关闭的流套接字写不产生 SIGPIPE 信号
// MSG_OOB 带外数据(紧急数据)
// send() 等价于以下调用:
// sendto(sockfd, buf, len, flags, NULL, 0);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
// 既可以用于 UDP 也可以用于 TCP
// 将数据大小为 len 的 buf 中的数据通过 sockfd 发送到大小为 addrlen
// 的地址 dest_addr
// send() 和 sendto() 返回成功发送的字节数,或失败返回 -1
#endif
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in svr_addr, clt_addr;
in_port_t port;
in_addr_t inaddr;
socklen_t socklen = sizeof svr_addr;
char *ip;
char recv_buf[32] = "";
char send_buf[32] = "Hello server";
// 检查参数个数
if (argc != 3)
{
printf("用法:%s <服务器 IP> <服务器端口号>\n", argv[0]);
return -1;
}
ip = argv[1];
port = atoi(argv[2]);
// 创建 UDP 套接字
sockfd = socket(AF_INET /*PF_INET*/, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("创建套接字失败");
return -1;
}
// 客户端不必绑定本机地址
// 配置服务器地址
inaddr = inet_addr(ip);
if (INADDR_NONE == inaddr)
{
printf("%s 是非法 IP\n", ip);
goto _out;
}
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(port);
svr_addr.sin_addr.s_addr = inaddr;
// 发
if (-1 == sendto(sockfd, send_buf, strlen(send_buf), 0,
(struct sockaddr *) &svr_addr, socklen))
{
perror("发送失败");
goto _out;
}
printf("发送成功\n");
// 收
if (-1 == recvfrom(sockfd, recv_buf, sizeof recv_buf, 0,
(struct sockaddr *) &clt_addr, &socklen))
{
perror("接收失败");
goto _out;
}
printf("收到 %s(%d) 发来的消息:%s\n",
inet_ntoa(clt_addr.sin_addr),
ntohs(clt_addr.sin_port), recv_buf);
_out:
// 关闭套接字
close(sockfd);
return 0;
}