一、TCP/IP协议简介
什么是TCP/IP
TCP/IP协议是一种用于因特网的通信协议。TCP指传输控制协议(Transmission Control Protocol),IP指网际协议(Internet Protocol)。
TCP/IP协议簇中的协议
TCP/IP协议是一个协议簇,其中包含许多协议,囊括了应用层、传输层、网络层以及网络访问层。
- 应用层包括:
①超文本传输协议(HTTP),万维网的基本协议
②TFTP文件传输协议
③Telnet远程登录协议
④SNMP网络管理协议
⑤DNS域名解析
- 网络层包括:
①网际(IP)协议
②因特网消息控制协议ICMP
③ARP地址解析协议
④RARP反向地址解析协议
- 网络访问层:
网络访问层是TCP/IP协议簇的最底层,它提供物理网络的接口,实现对复杂数据的发送和接收。网络访问层协议为网络接口、数据传输提供了对应的技术规范。TCP/IP中的网络访问层对应OSI七层网络模型中的物理层和数据链路层。
TCP、UDP的区别
TCP、UDP都是TCP/IP协议簇中的通信协议,其中TCP(Transmission Control Protocol)是面向连接的协议,而UDP(User Data Protocol)则是一个非连接的协议。
二、TCP协议应用
TCP的三次握手
TCP协议在进行数据通信之前必须建立连接,而TCP建立连接采用“三次握手”的方式。
三次握手的过程就像打电话,如下:
小刘打电话给王哥,这个打电话的动作即发起连接请求(第一次握手),王哥听到来电铃声接起电话并说:“喂,小刘啊,听得到吗?”,这一步骤就如上图服务端响应客户端的连接请求(第二次握手),这时小刘听见电话里传来王哥的声音,知道王哥已经响应,便说道:“王哥,可以听到。”,这一步即上图中客户端对服务端的第三次握手,此时连接便已成功建立,小刘(客户端)和王哥(服务端)就可以进行通信了。
TCP的四次挥手
如同三次挥手像打电话一样,四次挥手也可以像挂断电话一般。如下:
小刘(客户端):王哥,事情大概就是这个样子。
王哥(服务端):好,这个事情你放心,没问题。
王哥(服务端):小刘,没啥事的话我就挂了啊。
小刘(客户端):好嘞,再见王哥。
此时连接断开。
拨打/挂断电话跟TCP的三次握手/四次挥手过程不完全一致,但可以用这个过程去初步理解,初步理解后再根据其原理进行深入理解。
TCP服务器
TCP服务器建立的流程是创建套接字、绑定套接字、监听套接字,然后进行收发消息、处理消息的操作。
1、创建套接字
函数原型,TCP协议的通信依靠套接字,在使用套接字之前,需要先使用socket()函数创建套接字,使用该函数时需要传入地址族(af)、数据传输方式/套接字类型(type)、传输协议三个参数(protocol)。
af为地址族(Address Family),即IP地址类型,常用的有AF_INET和AF_INET6。AF是"Address Family"的简写,INET是"Internet"的简写。AF_INET表示的是IPV4地址,AF_INET6则表示的是IPV6地址。(也可以使用PF(Protocol Family)前缀,与AF一致)
type为数据传输方式/套接字类型,常用的有SOCK_STREAM(数据流式套接字/面向连接的套接字)和SOCK_DGRAM(数据报式套接字/无连接的套接字)。
protocol为传输协议,常用的有IPPROTO_TCP和IPPROTO_UDP,分别表示TCP传输协议和UDP传输协议。在使用TCP或UDP协议时,由于SOCK_STREAM和SOCK_DGRAM分别只能用于TCP、UDP,因此该参数可以填0,系统会自行适配传输协议。
int socket(int af, int type, int protocol);
INT sockfd = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(0 > sockfd)
{
SaveLog(MODULE_TCPSERVER, "ERROR:Create socket fail\r\n");
return -1;
}
2、绑定、监听套接字
函数原型,sock为socket文件描述符,addr为sockaddr结构体变量的指针,addrlen为addr变量的大小。
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
struct sockaddr_in ServerAddr = {0};
memset(&ServerAddr, 0, sizeof(ServerAddr));
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(SERVER_PORT);
ServerAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
if(0 > bind(sockfd, (struct sockaddr*)&ServerAddr,sizeof(ServerAddr)))
{
SaveLog(MODULE_TCPSERVER, "ERROR:Bind failed\r\n");
return -1;
}
函数原型,sock为socket文件描述符,backlog为请求队列的最大长度。
int listen(int sock, int backlog);
if(0 < listen(sockfd, SERVER_MAX_CON))
{
SaveLog(MODULE_TCPSERVER, "ERROR:Listen failed\r\n");
return -1;
}
return sockfd;
3、接收连接
TCP服务器完成套接字的创建、绑定与监听操作后,需要调用accept函数接收客户端的连接,函数原型为:
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
accpet()函数的参数与listen()函数的一致,不过返回值为客户端的socket描述符。
INT connfd = 0;
connfd = accept(sockfd, NULL, NULL);
if(0 > connfd)
{
SaveLog(MODULE_TCPSERVER, "ERROR:accept failed\r\n");
}
4、数据传输
连接建立后,即可进行数据的传输。主要使用recv()函数和send()函数。函数原型如下:
int send(SOCKET sock, const char *buf, int len, int flags);
int recv(SOCKET sock, char *buf, int len,int flags);
其中,sock为套接字,buf为要发送或者存储接收数据的缓冲区地址,len为发送/接收的数据的字节数,flags为发送/接收数据的选项(一般为0或者NULL即可)。recv()函数和send()函数的返回值都为接收/发送的实际字节数。
struct sockaddr ClientAddr = {0};
struct sockaddr_in ClientAddrIn = {0};
INT iRes = 0;
ULONG ulNameLen = 0;
CHAR *szBuf[1024] = {0};
memset(&ClientAddr, 0, sizeof(ClientAddr));
memset(&ClientAddrIn, 0, sizeof(ClientAddrIn));
ulNameLen = sizeof(struct sockaddr);
connfd = (INT *)void_sockfd;
if(0 == getsockname(connfd, &ClientAddr, (socklen_t *)&ulNameLen))
{
memcpy(&ClientAddrIn, &ClientAddr, ulNameLen);
SaveLog(MODULE_TCPSERVER, "INFO:Client connect,IP:%s:%d\r\n",inet_ntoa(ClientAddrIn.sin_addr),ntohs(ClientAddrIn.sin_port));
}
else
{
SaveLog(MODULE_TCPSERVER, "ERROR:Get client IP address failed\r\n");
}
while(1)
{
iRes = recv(connfd, szBuf, sizeof(szBuf), 0);
if(0 > iRes)
{
SaveLog(MODULE_TCPSERVER, "ERROR:Recv buf From %s failed,iRes:%d\r\n", inet_ntoa(ClientAddrIn.sin_addr), iRes);
}
else if(iRes > 0)
{
SaveLog(MODULE_TCPSERVER, "INFO:Recv buf:%s From:%s\r\nLen:%d\r\n", szBuf, inet_ntoa(ClientAddrIn.sin_addr), iRes);
}
else
{
SaveLog(MODULE_TCPSERVER, "INFO:Connect From %s stop\r\n", inet_ntoa(ClientAddrIn.sin_addr));
return;
}
}
服务器实例:
#include "tcp_server.h"
#include "../log/log.h"
VOID main()
{
INT sockfd = 0;
sockfd = Socket_Init();
if(0 > sockfd)
{
printf("exec failed\r\n");
return;
}
Socket_Process(sockfd);
}
INT Socket_Init()
{
INT sockfd = 0;
struct sockaddr_in ServerAddr = {0};
memset(&ServerAddr, 0, sizeof(ServerAddr));
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(0 > sockfd)
{
SaveLog(MODULE_TCPSERVER, "ERROR:Create socket fail\r\n");
return -1;
}
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(SERVER_PORT);
ServerAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
if(0 > bind(sockfd, (struct sockaddr*)&ServerAddr,sizeof(ServerAddr)))
{
SaveLog(MODULE_TCPSERVER, "ERROR:Bind failed\r\n");
return -1;
}
if(0 < listen(sockfd, SERVER_MAX_CON))
{
SaveLog(MODULE_TCPSERVER, "ERROR:Listen failed\r\n");
return -1;
}
return sockfd;
}
VOID Socket_Process(INT sockfd)
{
INT connfd = 0;
pthread_t th_process;
while(1)
{
connfd = accept(sockfd, NULL, NULL);
if(0 > connfd)
{
SaveLog(MODULE_TCPSERVER, "ERROR:accept failed\r\n");
}
/* 连接成功后进入线程 */
pthread_create(&th_process, NULL, Server_Process, (VOID *)connfd);
}
}
VOID Server_Process(VOID *void_sockfd)
{
INT connfd = 0;
struct sockaddr ClientAddr = {0};
struct sockaddr_in ClientAddrIn = {0};
INT iRes = 0;
ULONG ulNameLen = 0;
CHAR *szBuf[1024] = {0};
memset(&ClientAddr, 0, sizeof(ClientAddr));
memset(&ClientAddrIn, 0, sizeof(ClientAddrIn));
ulNameLen = sizeof(struct sockaddr);
connfd = (INT *)void_sockfd;
if(0 == getsockname(connfd, &ClientAddr, (socklen_t *)&ulNameLen))
{
memcpy(&ClientAddrIn, &ClientAddr, ulNameLen);
SaveLog(MODULE_TCPSERVER, "INFO:Client connect,IP:%s:%d\r\n",inet_ntoa(ClientAddrIn.sin_addr),ntohs(ClientAddrIn.sin_port));
}
else
{
SaveLog(MODULE_TCPSERVER, "ERROR:Get client IP address failed\r\n");
}
while(1)
{
iRes = recv(connfd, szBuf, sizeof(szBuf), 0);
if(0 > iRes)
{
SaveLog(MODULE_TCPSERVER, "ERROR:Recv buf From %s failed,iRes:%d\r\n", inet_ntoa(ClientAddrIn.sin_addr), iRes);
}
else if(iRes > 0)
{
SaveLog(MODULE_TCPSERVER, "INFO:Recv buf:%s From:%s\r\nLen:%d\r\n", szBuf, inet_ntoa(ClientAddrIn.sin_addr), iRes);
}
else
{
SaveLog(MODULE_TCPSERVER, "INFO:Connect From %s stop\r\n", inet_ntoa(ClientAddrIn.sin_addr));
return;
}
}
}
TCP客户端
如同服务器,TCP客户端在数据传输之前也需要创建套接字,不同于服务器的是,客户端创建套接字后无需绑定、监听操作,直接使用connect()函数连接至服务器即可。
/************************
函数名:connect
参数:sock 套接字
server_addr 服务器地址结构体
addrlen 服务器地址结构体大小
返回值:0为成功,小于0失败
************************/
int connect(int sock, struct sockaddr *server_addr, socklen_t addrlen);
客户端实例
VOID main()
{
INT sockfd = 0;
sockfd = ConnectServer();
Client_Process(sockfd);
}
VOID Client_Process(INT sockfd)
{
INT i = 0;
INT iRes = 0;
INT iMsgLen = 0;
CHAR szBuf[1024] = {0};
CHAR szHead[TCP_MSG_HEAD_LEN] = {0};
sprintf(szBuf, "This is a hello msg!\r\nIs used to do test for server");
/* 组装消息头 */
iMsgLen = strlen(szBuf);
PackMsgHead(MSG_PRINT, iMsgLen, szHead);
/* 发送消息头 */
iRes = send(sockfd, szHead, TCP_MSG_HEAD_LEN, 0);
if(0 > iRes)
{
SaveLog(MODULE_TCPCLIENT, "ERROR:Send buf failed,iRes:%d\r\n", iRes);
return;
}
SaveLog(MODULE_TCPCLIENT, "INFO:Send head:%s\r\nLen:%d\r\n", szHead, iRes);
sleep(1);
/* 发送消息体 */
iRes = send(sockfd, szBuf, iMsgLen, 0);
if(0 > iRes)
{
SaveLog(MODULE_TCPCLIENT, "ERROR:Send buf failed,iRes:%d\r\n", iRes);
return;
}
SaveLog(MODULE_TCPCLIENT, "INFO:Send buf:%s\r\nLen:%d\r\n", szBuf, iRes);
sleep(1);
while(1);
}
INT ConnectServer()
{
INT sockfd = 0;
struct sockaddr_in cli_addr;
memset(&cli_addr, 0, sizeof(cli_addr));
cli_addr.sin_family = AF_INET;
cli_addr.sin_port = htons(SERVER_PORT);
cli_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
SaveLog(MODULE_TCPCLIENT, "ERROR:Create socket fail\r\n");
return -1;
}
if(0 > connect(sockfd, (struct sockaddr *)&cli_addr, sizeof(cli_addr)))
{
SaveLog(MODULE_TCPCLIENT, "ERROR:connect server failed\r\n");
return -1;
}
SaveLog(MODULE_TCPCLIENT, "INFO:connect server success\r\n");
return sockfd;
}
VOID PackMsgHead(TCP_MSG_TYPE_E eMsgType, ULONG ulMsgLen, CHAR *pcOutBuf)
{
if(NULL == pcOutBuf)
{
SaveLog(MODULE_TCPCLIENT, "Buf is null");
return;
}
TCP_MSG_HEAD_S stTcpMsgHead;
memset(&stTcpMsgHead, 0, sizeof(stTcpMsgHead));
stTcpMsgHead.eMsgType = eMsgType;
stTcpMsgHead.ulMsgLen = ulMsgLen;
memcpy(pcOutBuf, (CHAR *)&stTcpMsgHead, sizeof(stTcpMsgHead));
}
相关结构体、宏定义等
/* tcp_client.h */
#ifndef _TCP_CLIENT_H_
#define _TCP_CLIENT_H_
#include "../../common/common.h"
#define SERVER_PORT 14000 //TCP服务器使用的端口号
#define SERVER_IP "192.168.1.80" //服务器IP地址
INT ConnectServer();
VOID Client_Process(INT sockfd);
#endif
/* tcp_server.h */
#ifndef _TCP_SERVER_H_
#define _TCP_SERVER_H_
#include "../../common/common.h"
#define SERVER_PORT 14000
#define SERVER_MAX_CON 100 //最大连接数
#define SERVER_IP "192.168.0.80"
#define MAX_LOG_SIZE 4096
INT Socket_Init();
VOID Socket_Process(INT sockfd);
VOID Server_Process(VOID *void_sockfd);
#endif
/* tcp.h */
#ifndef _TCP_H_
#define _TCP_H_
#include "../common/common.h"
#define TCP_MSG_HEAD_LEN sizeof(TCP_MSG_HEAD_S)
typedef enum
{
MSG_PRINT = 0xFF00, //0xFF00
MSG_ERROR //0xFF01
}TCP_MSG_TYPE_E;
typedef struct tagTcpMsgHead
{
TCP_MSG_TYPE_E eMsgType;
ULONG ulMsgLen;
}TCP_MSG_HEAD_S;
#endif
/* common.h */
#ifndef _COMMON_H_
#define _COMMON_H_
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <stddef.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdarg.h>
#include <pthread.h>
#include <time.h>
#include <uinstd.h>
#define INT int
#define CHAR char
#define FLOAT float
#define DOUBLE double
#define SHORT short
#define LONG long
#define UINT unsigned int
#define ULONG unsigned long
#define UCHAR unsigned char
#define USHORT unsigned short
#define VOID void
#define STATIC static
#define CONST const
extern INT Exec_Shell(CONST CHAR* pcCmd, CHAR *pcOutBuf, INT iLen);
#endif
日志功能
/* log.c */
#include "log.h"
VOID LogWright(CHAR *pcModule, CHAR *pcFile, CHAR *pcFunc, INT iLine, CONST CHAR *pcFormat, ...)
{
va_list argList = {0};
CHAR szLogTemp[MAX_LOG_SIZE] = {0};
CHAR szLogHeader[256] = {0};
CHAR szFileName[64] = {0};
FILE *fpLogFile;
CHAR szTime[64] = {0};
INT iRes = 0;
struct tm *stNowTime;
time_t nowTime;
time(&nowTime);
stNowTime = localtime(&nowTime);
sprintf(szFileName, "../log/%04d%02d%02d%s.log", stNowTime->tm_year + 1900, stNowTime->tm_mon + 1, stNowTime->tm_mday, pcModule);
sprintf(szTime, "%d-%d-%d %d:%d:%d", stNowTime->tm_year + 1900, stNowTime->tm_mon + 1, stNowTime->tm_mday, stNowTime->tm_hour, stNowTime->tm_min, stNowTime->tm_sec);
va_start(argList, pcFormat);
vsnprintf(szLogTemp, MAX_LOG_SIZE, pcFormat, argList);
va_end(argList);
snprintf(szLogHeader, sizeof(szLogHeader), "[%s] [FILE: %s] [FUNC: %s] [LINE: %d] ------ ", szTime, pcFile, pcFunc, iLine);
fpLogFile = fopen(szFileName, "a+");
if(NULL == fpLogFile)
{
printf("FILE:tcp_client_log.c LINE:%d fopen %s error!\r\n", __LINE__, szFileName);
return;
}
fwrite(szLogHeader, 1, strlen(szLogHeader), fpLogFile);
fwrite(szLogTemp, 1, strlen(szLogTemp), fpLogFile);
fclose(fpLogFile);
}
/* log.h */
#ifndef _LOG_H_
#define _LOG_H_
#include "../../common/common.h"
#define MAX_LOG_SIZE 4096
#define MODULE_TCPSERVER "tcp_server"
#define MODULE_TCPCLIENT "tcp_client"
#define SaveLog(module, format, ...)\
{\
LogWright((CHAR *)module, (CHAR *)__FILE__, (CHAR *)__FUNCTION__, (INT)__LINE__, format, ##__VA_ARGS__);\
}
extern VOID LogWright(CHAR *pcModule, CHAR *pcFile, CHAR *pcFunc, INT iLine, CONST CHAR *pcFormat, ...);
#endif