socket概念
从wiki上了解,socket这个词追溯到 1971 年 RFC 147 的发布。
目前我的理解:常用于指操作系统提供的 API,该 API 允许使用 TCP、UDP 进行连接,但不仅限于 TCP、UDP 协议。
实现目的
利用系统提供函数接口,通过C语言实现对TCP 服务器(IP地址)的连接,以及收发数据。
实现过程
1、socket(2) 创建套接字
2、connect(2) 连接服务器。服务器已打开,否则会直接返回错误。
3、send(2) 向服务器发送数据。连接成功后,即可与服务器通信。
4、recv(2) 接收服务器发送过来的数据。
5、close(2) 关闭套接字。
实现代码
/****************************************************************************** file name: mytcp_client.c* author : crazy3min@outlook.com* date : 2024-06-05* function : TCP协议的客户端操作。* note :* 测试编译指令: gcc ./src/mytcp_client.c ./src/mytime.c -o ./bin/mytcp_client -I ./include* 通过命令行输入服务器ip和端口,示例:./bin/mytcp_client IP PORT** CopyRight (c) 2024 crazy3min@outlook.com Right Reseverd*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdbool.h>
#include <pthread.h>
#include <netinet/in.h>
#include "mytime.h" //时间头文件#define DEBUG // 开启调试模式/******************************* 全局变量 START *******************************/
char timebuf[128]; // 时间输出缓冲区
/******************************* 全局变量 END *******************************//****************************************************************************** function name : tcp_v4_hton* function : 将ipv4服务器的信息从本地字节序转换为网络字节序,并存储在destinfo指针下。* parameter :* @destinfo: 存储转换后的信息指针。* @address: 需要转换的点分十进制IPv4地址,例如 "192.168.5.1"* @port: 需要转换的端口,例如:60000** return value : None* note : None** author : crazy3min@outlook.com* date : 2024-06-06* version : V1.0* revision history : None*****************************************************************************/
void tcp_v4_hton(struct sockaddr_in *destinfo, const char *address, const int port)
{destinfo->sin_family = AF_INET; // 协议,AF_INET代表IPV4协议destinfo->sin_port = htons(port); // 服务器端口,必须将目标端口转为网络字节序(大端)destinfo->sin_addr.s_addr = inet_addr(address); // 服务器ip,必须将目标ip转为网络字节序(大端)
}/****************************************************************************** function name : tcp_v4_connect* function : 连接IPv4 TCP服务器* parameter :* @socketfd: socket指针。* @destinfo: 储存 IPv4 TCP服务器信息的指针** return value : 成功返回true,失败返回false* note : None** author : crazy3min@outlook.com* date : 2024-06-06* version : V1.0* revision history : None*****************************************************************************/
bool tcp_v4_connect(int *socketfd, struct sockaddr_in *destinfo)
{// 创建ipv4 TCP 通信端点*socketfd = socket(destinfo->sin_family, SOCK_STREAM, 0);if (-1 == *socketfd){fprintf(stderr,"[%s] [%s] 创建ipv4 TCP 通信端点,Error code: %d, Error message: %s\n",__FILE__,__func__,errno,strerror(errno));return false;}// 请求连接 tcp服务器if (-1 == connect(*socketfd, (const struct sockaddr *)destinfo, sizeof(struct sockaddr_in))){fprintf(stderr,"[%s] [%s] 连接ipv4 TCP 通信端点失败,Error code: %d, Error message: %s\n",__FILE__,__func__,errno,strerror(errno));return false;}// 成功连接
#ifdef DEBUGtime_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");printf("[DEBUG][%s]成功连接服务器 \n", timebuf);
#endifreturn true;
}/****************************************************************************** function name : tcp_v4_send* function : 向IPv4 TCP服务器发送一条数据* parameter :* @socketfd: 已连接服务器的socket句柄* @buf: 存储待发送数据的缓冲区指针。* @bufsize: 数据的大小,单位字节。** return value : 成功返回true,失败返回false* note :* 必须先使用tcp_v4_connect()连接服务器后再使用。** author : crazy3min@outlook.com* date : 2024-06-06* version : V1.0* revision history : None*****************************************************************************/
bool tcp_v4_send(const int socketfd, const char *buf, const int bufsize)
{if (bufsize != send(socketfd, buf, bufsize, 0)){
#ifdef DEBUGtime_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");printf("[DEBUG][%s] 发送 [%s] 失败!!!\n", timebuf, buf);
#endifreturn false;}#ifdef DEBUGtime_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");printf("[DEBUG][%s] 成功发送 [%s] \n", timebuf, buf);
#endifreturn true;
}/****************************************************************************** function name : tcp_client_recv* function : 线程任务,连接TCP IPv4服务器后,接收服务器发来的信息并输出。* parameter :* @arg: 已连接服务器的socket句柄指针** return value : None* note :* 必须先使用tcp_v4_connect()连接服务器后再使用。** author : crazy3min@outlook.com* date : 2024-06-06* version : V1.0* revision history : None*****************************************************************************/
void *tcp_client_recv(void *arg)
{int socketfd = *((int *)arg); // 转换通过参数传入socket套接字句柄char buffer[512] = {0}; // 接收数据缓冲区// 循环阻塞等待服务器发送的数据while (1){if (0 == (recv(socketfd, buffer, sizeof(buffer), 0))){// 服务器终止连接,结束程序time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");printf("[%s] 服务器已经终止连接\n", timebuf);close(socketfd);exit(EXIT_SUCCESS);}time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");printf("[%s]收到服务器发来的信息:[%s]\n", timebuf, buffer);bzero(buffer, sizeof(buffer)); // 清空缓存}
}int main(int argc, char const *argv[])
{// 通过终端传入服务器的信息if (3 != argc){printf("参数无效,请输入 [./xxx IP PORT] 执行\n");return -1;}int socketfd; // 创建套接字char buffer[512] = {0}; // 发送数据缓冲区struct sockaddr_in destinfo; // 定义IPv4 地址和端口的结构体 变量保存服务端信息tcp_v4_hton(&destinfo, argv[1], atoi(argv[2])); // 转换为网络字节序// 连接服务器if (!(tcp_v4_connect(&socketfd, &destinfo)))return -1;// 创建线程接收服务器发送的信息pthread_t tcp_recv_task;if (0 != (pthread_create(&tcp_recv_task, NULL, tcp_client_recv, &socketfd))){printf("********** 创建接收服务器发送的信息线程失败! **********\n");}
#ifdef DEBUGelse{time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");printf("[DEBUG][%s] 成功创建接收服务器发送的信息线程 \n", timebuf);}
#endif// 需要发送的信息while (1){printf("请输入发送的数据:\n");fgets(buffer, sizeof(buffer), stdin); // 标准输入获取数据buffer[strcspn(buffer, "\n")] = '\0'; // 替换换行符if (0 == strlen(buffer)){printf("********** 请输入有效信息 **********\n");}else if (!(tcp_v4_send(socketfd, buffer, strlen(buffer)))){time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");printf("[%s] 服务器已经终止连接\n", timebuf);close(socketfd);break;}else{bzero(buffer, sizeof(buffer)); // 清空缓存}}return 0;
}
测试结果
参考信息
- TCP/IP 简介(第 4 部分)- 套接字和端口
衍生问题
- 什么是Berkeley Sockets?
Berkeley 套接字是用于创建和使用套接字的行业标准应用程序编程接口 (API)。它最初被用作 Unix 操作系统的 API,后来被 TCP/IP 采用。