最近在学习套接字相关的项目,以下是我对TCP协议的个人小结,有不合理之处欢迎指出,共同进步!!!
分为服务端和客户端,以下为基本的逻辑。
服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8080
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
char *hello = "Hello from server";
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 绑定套接字到端口
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 读取数据(这里我们假设客户端已经发送了数据)
read(new_socket, buffer, 1024);
printf("%s\n", buffer);
// 发送数据到客户端
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 关闭套接字
close(new_socket);
close(server_fd);
return 0;
}
客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 8080
int main() {
struct sockaddr_in serv_addr;
int sock = 0;
char *hello = "Hello from client";
char buffer[1024] = {0};
// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 将IPv4地址从文本转换成二进制形式
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {
printf("\nInvalid address/ Address not supported \n");
return -1;
}
// 连接到服务器 (对应服务端的连接请求:listen)
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nConnection Failed \n");
return -1;
}
// 发送数据到服务器
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 接收来自服务器的数据
int valread = read(sock, buffer, 1024);
printf("%s\n", buffer);
// 关闭套接字
close(sock);
return 0;
}
属性/检索:
一般在套接字创建之后,绑定之前会进行套接字属性的设置socketopt()
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
例如:设置套接字IP复用
int sock = socket(AF_INET, type, protocol);
if (sock < 0){
printf("fail...\n");
return -1;
}
int optval = 1;
/* 允许套接字使用相同的 IP 地址 */
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(optval)) < 0){
printf("SO_REUSEADDR FAIL...\n");
}
以及在服务端和客户端进行读或者写的时候进行可读或者可写的判断select()
/**
* @brief 用select接口
* @param fd
* @param timeout_ms 超时时间
* @param read_flg 读?
* @param write_flg 写?
* @return int
* @note select可以避免程序一直阻塞在某个sock的读写操作上
*/
int user_socket_select(int fd, int timeout_ms, bool read_flg, bool write_flg){
fd_set fdset_read;
fd_set fdset_write;
FD_ZERO(&fdset_read); /* 将fd_set变量的所有位初始化位0 */
FD_ZERO(&fdset_write);
FD_SET(fd, &fdset_read); /* 在参数fdset指向的变量中注册文件描述符fd的信息 */
FD_SET(fd, &fdset_write);
struct timeval timeout;
timeout.tv_sec = timeout_ms / 1000;
timeout.tv_usec = timeout_ms % 1000 * 1000;
return select(fd+1, (read_flg ? &fdset_read:NULL), (write_flg ? &fdset_write:NULL), NULL, &timeout);
}