天天看點

C語言實作TCP通信

C語言通過socket程式設計實作TCP通信

服務端用戶端通信例子:socket tcp 通信1,socket tcp通信2,udp使用講解,socket udp通信例子

  1. TCP/IP協定

叫做傳輸控制/網際協定,又叫網絡通信協定。實際上,它包含上百個功能的協定,如ICMP(網際網路控制資訊協定)、FTP(檔案傳輸協定)、UDP(使用者資料包協定)、ARP(位址解析協定)等。TCP負責發現傳輸的問題,一旦有問題就會發出重傳信号,直到所有資料安全正确的傳輸到目的地。

2.套接字(socket):

在網絡中用來描述計算機中不同程式與其他計算機程式的通信方式。socket其實是一種特殊的IO接口,也是一種檔案描述符。
           

套接字分為三類:

流式socket(SOCK_STREAM):流式套接字提供可靠、面向連接配接的通信流;它使用TCP協定,進而保證了資料傳輸的正确性和順序性。

資料報socket(SOCK_DGRAM):資料報套接字定義了一種無連接配接的服務,資料通過互相獨立的保溫進行傳輸,是無序的,并且不保證是可靠、無差錯的。它使用的資料報協定是UDP。

原始socket:原始套接字允許對底層協定如IP或ICMP進行直接通路,它功能強大但使用複雜,主要用于一些協定的開發。
           

套接字由三個參數構成:IP位址,端口号,傳輸層協定。這三個參數用以區分不同應用程式程序間的網絡通信與連接配接。

套接字的資料結構:C語言進行套接字程式設計時,常會使用到sockaddr資料類型和sockaddr_in資料類型,用于儲存套接字資訊。

兩種結構體分别表示如下:

struct sockaddr
{
 
//位址族,2位元組
unsigned short sa_family;
 
//存放位址和端口,14位元組
char sa_data[14];
 
}
 
 
struct sockaddr_in
{
 
//位址族
short int sin_family;
 
//端口号(使用網絡位元組序)
unsigned short int sin_port;
 
//位址
struct in_addr sin_addr;
 
//8位元組數組,全為0,該位元組數組的作用隻是為了讓兩種資料結構大小相同而保留的空位元組
unsigned char sin_zero[8]
 
}
           

對于sockaddr,大部分的情況下隻是用于bind,connect,recvfrom,sendto等函數的參數,指明位址資訊,在一般程式設計中,并不對此結構體直接操作。而用sockaddr_in來替。

兩種資料結構中,位址族都占2個位元組,

常見的位址族有:AF_INET,AF_INET6AF_LOCAL。

這裡要注意位元組序的問題,最好使用以下函數來對端口和位址進行處理:

  1. uint16_t htons(uint16_t host16bit) 把16位值從主機位元組序轉到網絡位元組序

    uint32_t htonl(uint32_t host32bit) 把32位值從主機位元組序轉到網絡位元組序

  2. uint16_t ntohs(uint16_t net16bit) 把16位值從網絡位元組序轉到主機位元組序

    uint32_t ntohs(uint32_t net32bit) 把32位值從網絡位元組序轉到主機位元組序

使用socket進行TCP通信時,經常使用的函數有:

3.下面給出server和client的兩個例子

更詳細的例子

服務端:

/socket tcp伺服器端/

#include <sys/stat.h>
 
#include <fcntl.h>
 
#include <errno.h>
 
#include <netdb.h>
 
#include <sys/types.h>
 
#include <sys/socket.h>
 
#include <netinet/in.h>
 
#include <arpa/inet.h>
 
#include <stdio.h>
 
#include <string.h>
 
#include <stdlib.h>
 
#include <unistd.h>
 
#define SERVER_PORT 6666
 
/*
監聽後,一直處于accept阻塞狀态,
直到有用戶端連接配接,
當用戶端如數quit後,斷開與用戶端的連接配接
*/
 
int main()
 
{
 
//調用socket函數傳回的檔案描述符
 
int serverSocket;
 
//聲明兩個套接字sockaddr_in結構體變量,分别表示用戶端和伺服器
 
struct sockaddr_in server_addr;
 
struct sockaddr_in clientAddr;
 
int addr_len = sizeof(clientAddr);
 
int client;
 
char buffer[200];
 
int iDataNum;
 
//socket函數,失敗傳回-1
 
//int socket(int domain, int type, int protocol);
 
//第一個參數表示使用的位址類型,一般都是ipv4,AF_INET
 
//第二個參數表示套接字類型:tcp:面向連接配接的穩定資料傳輸SOCK_STREAM
 
//第三個參數設定為0
 
if((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
 
{
 
perror("socket");
 
return 1;
 
}
 
bzero(&server_addr, sizeof(server_addr));
 
//初始化伺服器端的套接字,并用htons和htonl将端口和位址轉成網絡位元組序
 
server_addr.sin_family = AF_INET;
 
server_addr.sin_port = htons(SERVER_PORT);
 
//ip可是是本伺服器的ip,也可以用宏INADDR_ANY代替,代表0.0.0.0,表明所有位址
 
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 
//對于bind,accept之類的函數,裡面套接字參數都是需要強制轉換成(struct sockaddr *)
 
//bind三個參數:伺服器端的套接字的檔案描述符,
 
if(bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
 
{
 
perror("connect");
 
return 1;
 
}
 
//設定伺服器上的socket為監聽狀态
 
if(listen(serverSocket, 5) < 0)
 
{
 
perror("listen");
 
return 1;
 
}
 
while(1)
 
{
 
printf("監聽端口: %d\n", SERVER_PORT);
 
//調用accept函數後,會進入阻塞狀态
 
//accept傳回一個套接字的檔案描述符,這樣伺服器端便有兩個套接字的檔案描述符,
 
//serverSocket和client。
 
//serverSocket仍然繼續在監聽狀态,client則負責接收和發送資料
 
//clientAddr是一個傳出參數,accept傳回時,傳出用戶端的位址和端口号
 
//addr_len是一個傳入-傳出參數,傳入的是調用者提供的緩沖區的clientAddr的長度,以避免緩沖區溢出。
 
//傳出的是用戶端位址結構體的實際長度。
 
//出錯傳回-1
 
client = accept(serverSocket, (struct sockaddr*)&clientAddr, (socklen_t*)&addr_len);
 
if(client < 0)
 
{
 
perror("accept");
 
continue;
 
}
 
printf("等待消息...\n");
 
//inet_ntoa ip位址轉換函數,将網絡位元組序IP轉換為點分十進制IP
 
//表達式:char *inet_ntoa (struct in_addr);
 
printf("IP is %s\n", inet_ntoa(clientAddr.sin_addr));
 
printf("Port is %d\n", htons(clientAddr.sin_port));
 
while(1)
 
{
 
printf("讀取消息:");
 
buffer[0] = '\0';
 
iDataNum = recv(client, buffer, 1024, 0);
 
if(iDataNum < 0)
 
{
 
perror("recv null");
 
continue;
 
}
 
buffer[iDataNum] = '\0';
 
if(strcmp(buffer, "quit") == 0)
 
break;
 
printf("%s\n", buffer);
 
 
 
printf("發送消息:");
 
scanf("%s", buffer);
 
printf("\n");
 
send(client, buffer, strlen(buffer), 0);
 
if(strcmp(buffer, "quit") == 0)
 
break;
 
}
 
}
 
close(serverSocket);
 
return 0;
 
}
           

用戶端:

/socket tcp用戶端/

#include <sys/stat.h>
 
#include <fcntl.h>
 
#include <errno.h>
 
#include <netdb.h>
 
#include <sys/types.h>
 
#include <sys/socket.h>
 
#include <netinet/in.h>
 
#include <arpa/inet.h>
 
#include <stdio.h>
 
#include <string.h>
 
#include <stdlib.h>
 
#include <unistd.h>
 
#define SERVER_PORT 6666
 
/*
連接配接到伺服器後,會不停循環,等待輸入,
輸入quit後,斷開與伺服器的連接配接
*/
 
int main()
 
{
 
//用戶端隻需要一個套接字檔案描述符,用于和伺服器通信
 
int clientSocket;
 
//描述伺服器的socket
 
struct sockaddr_in serverAddr;
 
char sendbuf[200];
 
char recvbuf[200];
 
int iDataNum;
 
if((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
 
{
 
perror("socket");
 
return 1;
 
}
 
serverAddr.sin_family = AF_INET;
 
serverAddr.sin_port = htons(SERVER_PORT);
 
//指定伺服器端的ip,本地測試:127.0.0.1
 
//inet_addr()函數,将點分十進制IP轉換成網絡位元組序IP
 
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
 
if(connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
 
{
 
perror("connect");
 
return 1;
 
}
 
printf("連接配接到主機...\n");
 
while(1)
 
{
 
printf("發送消息:");
 
scanf("%s", sendbuf);
 
printf("\n");
 
send(clientSocket, sendbuf, strlen(sendbuf), 0);
 
 
 
if(strcmp(sendbuf, "quit") == 0)
 
break;
 
printf("讀取消息:");
 
recvbuf[0] = '\0';
 
iDataNum = recv(clientSocket, recvbuf, 200, 0);
 
recvbuf[iDataNum] = '\0';
 
printf("%s\n", recvbuf);
 
}
 
close(clientSocket);
 
return 0;
 
}