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 DEBUG
time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");
printf("[DEBUG][%s]成功連線伺服器 \n", timebuf);
#endif
return 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 DEBUG
time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");
printf("[DEBUG][%s] 傳送 [%s] 失敗!!!\n", timebuf, buf);
#endif
return false;
}
#ifdef DEBUG
time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");
printf("[DEBUG][%s] 成功傳送 [%s] \n", timebuf, buf);
#endif
return 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 DEBUG
else
{
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 採用。