通过TCP协议进行C/S模式的网络通信
1.sockaddr_in 结构体
struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
uint16_t sin_port; //16位的端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};
in_addr 结构体
struct in_addr{
in_addr_t s_addr; //32位的IP地址
};
in_addr_t 在头文件
中定义,等价于 unsigned long,长度为4个字节。也就是说,s_addr 是一个整数,而IP地址是一个字符串,所以需要 inet_addr() 函数进行转换.
By the way, ntohs() 本函数将一个16位数由网络字节顺序转换为主机字节顺序
sockaddr 结构体的定义如下:
struct sockaddr{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
char sa_data[14]; //IP地址和端口号
};
这里我们使用 sockaddr_in 结构体,然后再强制转换为 sockaddr 类型
2…在 Linux 下使用
int socket(int pf, int type, int protocol);
pf 是“Protocol Family”的简写,也就是 IP 协议类型,常用的有PF_INET 、PF_INET6 。
af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6。
type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)
protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。在pf和type都只有一种协议满足条件,可以将 protocol 的值设为 0,系统会自动推演出应该使用什么协议。如下:
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字
使用 socket() 函数创建套接字以后,返回值就是一个 int 类型的文件描述符。
POSIX 定义了 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 来代替 0、1、2。这三个符号常量的定义位于头文件 unistd.h。
3.bind() 函数的原型为:
int bind(int sock, struct sockaddr *addr, socklen_t addrlen); //Linux
sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
4.通过 listen() 函数可以让套接字进入被动监听状态,它的原型为:
int listen(int sock, int backlog); //Linux
sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。
所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
5.当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。它的原型为:
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); //Linux
它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。
**accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。**后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
6.从服务器端发送数据使用 send() 函数,它的原型为:
int send(SOCKET sock, const char *buf, int len, int flags);
最后的 flags 参数一般设置为 0 或 NULL,初学者不必深究。
7.在客户端接收数据使用 recv() 函数,它的原型为:
int recv(SOCKET sock, char *buf, int len, int flags);
#include
#include
#include
int main()
{
int sockfd = -1;
int ret = -1;
int newfd = -1;
struct sockaddr_in seraddr;
//创建sockaddr_in结构体变量
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(1235);
seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
memset(seraddr.sin_zero, 0, 8);
//创建套接字
sockfd = socket(PF_INET, SOCK_STREAM, 0);
//将套接字和IP、端口绑定
ret = bind(sockfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
printf("bind err!");
close(sockfd);
return -1;
}
//进入监听状态,等待用户发起请求
ret = listen(sockfd, 1024);
if (-1 == ret)
{
printf("listen err!");
close(sockfd);
return -1;
}
//接收客户端请求
struct sockaddr_in clntaddr;
socklen_t clntaddr_size = sizeof(clntaddr);
newfd = accept(sockfd, (struct sockaddr*)&clntaddr, &clntaddr_size);
//接收客户端请求发送的数据
char szBuf[1024] = "\0";
char szReplyBuf[1024] = "hello, it's server!\0";
ret = recv(newfd, szBuf, sizeof(szBuf), 0);
if (ret > 0)
{
printf("recv \"%s\" from %s:%d\n",
szBuf,
(char*)inet_ntoa(clntaddr.sin_addr),
ntohs(clntaddr.sin_port));
}
//向客户端发送数据
ret = send(newfd, szReplyBuf, strlen(szReplyBuf), 0);
if (ret > 0)
{
printf("send \"%s\" to %s:%d\n",
szReplyBuf,
(char*)inet_ntoa(clntaddr.sin_addr),
ntohs(clntaddr.sin_port));
}
//关闭套接字
close(sockfd);
close(newfd);
return 0;
}
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); //Linux
#include
#include
#include
int main()
{
int sockfd = -1;
int ret = -1;
struct sockaddr_in seraddr;
struct sockaddr_in clntaddr;
//创建sockaddr_in结构体变量
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
seraddr.sin_port = htons(1235);
memset(seraddr.sin_zero, 0, 8);
//创建套接字
sockfd = socket(PF_INET, SOCK_STREAM, 0);
//向服务器(特定的IP和端口)发起请求
ret = connect(sockfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr));
if (-1 == ret)
{
printf("connect err!\n");
close(sockfd);
return -1;
}
char szBuf[1024] = "\0";
char szMsg[1024] = "Hi, it's client!";
//向服务器发送数据
ret = send(sockfd, szMsg, strlen(szMsg), 0);
if (ret > 0)
{
printf("send \"%s\" to %s:%d\n",
szMsg,
(char*)inet_ntoa(seraddr.sin_addr),
ntohs(seraddr.sin_port));
}
//读取服务器传回的数据
ret = recv(sockfd, szBuf, sizeof(szBuf), 0);
if (ret > 0)
{
printf("recv \"%s\" from %s:%d\n",
szBuf,
(char*)inet_ntoa(seraddr.sin_addr),
ntohs(seraddr.sin_port));
}
//关闭套接字
close(sockfd);
return 0;
}