采用 socket 实现服务端和客户端通信
server.c
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define BUF_SIZE 1024
#define MAX_PENDING 20
int main() {
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serv_sock == -1) {
perror("socket() error");
exit(EXIT_FAILURE);
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(1234);
if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
perror("bind() error");
close(serv_sock);
exit(EXIT_FAILURE);
}
if (listen(serv_sock, MAX_PENDING) == -1) {
perror("listen() error");
close(serv_sock);
exit(EXIT_FAILURE);
}
char buffer1[BUF_SIZE];
char buffer2[BUF_SIZE];
while (1) {
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock =
accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -1) {
perror("accept() error");
continue;
}
memset(buffer2, 0, BUF_SIZE);
printf("Enter a message: ");
fgets(buffer2, BUF_SIZE, stdin);
write(clnt_sock, buffer2, strlen(buffer2));
memset(buffer1, 0, BUF_SIZE);
read(clnt_sock, buffer1, BUF_SIZE);
printf("Client: %s\n", buffer1);
close(clnt_sock);
}
close(serv_sock);
return 0;
}
client.c
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define BUF_SIZE 1024
int main() {
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(1234);
char buffer1[BUF_SIZE];
char buffer2[BUF_SIZE];
while (1) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("socket() error");
continue;
}
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
perror("connect() error");
close(sock);
continue;
}
memset(buffer1, 0, BUF_SIZE);
read(sock, buffer1, BUF_SIZE);
printf("Server: %s\n", buffer1);
printf("Enter a message: ");
memset(buffer2, 0, BUF_SIZE);
fgets(buffer2, BUF_SIZE, stdin);
write(sock, buffer2, strlen(buffer2));
close(sock);
}
return 0;
}
这两个程序中的知识点:
socket 函数
函数原型:int socket(int af, int type, int protocol);
(1) af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET (IPv4)和 AF_INET6(IPv6)
也可以使用 PF 前缀,PF 是“Protocol Family”的简写,它和 AF 是一样的。例如,PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6
(2) type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)
(3) protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议
一般情况下有了 af 和 type 两个参数就可以创建套接字了,操作系统会自动推演出协议类型,除非遇到这样的情况:有两种不同的协议支持同一种地址类型和数据传输类型。如果我们不指明使用哪种协议,操作系统是没办法自动推演的。如果只有一种情况,protocol 可以用 0 来代替,例如:
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字
perror函数
EXIT_FAILURE
定义:EXIT_FAILURE
是一个宏,定义在头文件 stdlib.h
中。
用途:它通常用于 exit()
函数的参数,表示程序因某种失败条件而终止。
值:虽然其具体的值可能因实现而异,但通常它的值为 1
。这与成功退出的常量 EXIT_SUCCESS
(通常值为 0
)形成对比。
sockaddr_in
sockaddr_in 定义:
// 在 <netinet/in.h> 定义
struct sockaddr_in {
short sin_family; // 地址族(Address family),对于IPv4,其值为AF_INET
unsigned short sin_port; // 端口号,使用网络字节序,一般在 1024 - 65536 中选择
struct in_addr sin_addr; // IPv4地址结构体
char sin_zero[8]; // 用于将此结构体的大小与`sockaddr`对齐,通常设置为0
};
in_addr
// 在 <netinet/in.h> 定义
struct in_addr {
uint32_t s_addr; // IPv4地址,使用网络字节序
};
sockaddr
struct sockaddr{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
char sa_data[14]; //IP地址和端口号
};
地址转化
使用 inet_addr()
或 inet_pton()
函数将点分十进制的IPv4地址字符串转换为网络字节序的整数形式。
使用 inet_ntoa()
或 inet_ntop()
函数将网络字节序的整数转换为点分十进制的IPv4地址字符串。
struct in_addr ip_addr;
ip_addr.s_addr = inet_addr("192.168.1.1"); // 将点分十进制字符串转换为网络字节序的整数
bind 函数
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
socket() 函数用来创建套接字,确定套接字的各种属性,然后服务器端要用 bind() 函数将套接字与特定的 IP 地址和端口绑定起来,只有这样,流经该 IP 地址和端口的数据才能交给套接字处理。
为什么使用 sockaddr_in 而不使用 sockaddr:
sockaddr 和 sockaddr_in 的长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data 表示。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,遗憾的是,没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值,所以使用 sockaddr_in 来代替。这两个结构体的长度相同,强制转换类型时不会丢失字节,也没有多余的字节。
connect 函数
connect 函数用来将 client 端和 server 端建立连接:
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
listen 函数
int listen(int sock, int backlog);
通过 listen() 函数可以让套接字进入被动监听状态,所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。
缓冲区的长度(能存放多少个客户端请求)可以通过 listen() 函数的 backlog 参数指定,但究竟为多少并没有什么标准,可以根据你的需求来定,并发量小的话可以是10或者20。
如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。
accept 函数
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
fgets 函数
read/write 函数
ssize_t read(int fd, void *buf, size_t nbytes);
ssize_t write(int fd, const void *buf, size_t nbytes);
fd 为要读取的文件的描述符,buf 为要接收数据的缓冲区地址,nbytes 为要读取的数据的字节数。
read() 函数会从 fd 文件中读取 nbytes 个字节并保存到缓冲区 buf,成功则返回读取到的字节数(但遇到文件结尾则返回0),失败则返回 -1。
write() 函数会将缓冲区 buf 中的 nbytes 个字节写入文件 fd,成功则返回写入的字节数,失败则返回 -1。
标签:serv,sockaddr,socket,int,编程,sock,接字,addr From: https://www.cnblogs.com/hacker-dvd/p/17691617.html