文章目錄
上一篇文章【TCP通信】原理詳解與編程實現(一)總結了TCP通信的基本原理。本篇文章是編碼實現部分。
TCP把連接作爲最基本的抽象單元,每條TCP連接有兩個端點,TCP連接的端點即套接字。
套接字socket = (IP地址+端口號)
可以認爲,套接字就是網絡編程的接口,是連接網絡的一種工具。
本文先介紹linux下套接字相關函數,最後編碼實現一個簡單的TCP通信demo。windows下的方法相近。
1.套接字相關
1.1 socket() 函數
socket() 函數創建套接字,原型:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
1)domain 是協議族,也就是 IP 地址類型,常用的有 AF_INET(IPv4) 和 AF_INET6(IPv6)
2)type 爲數據傳輸方式,常用的有 SOCK_STREAM (面向連接的數據傳輸方式)和 SOCK_DGRAM(無連接的數據傳輸方式)
3)protocol 表示傳輸協議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協議和 UDP 傳輸協議
舉例:
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
1.2 bind() 函數
bind() 函數.服務器端 將套接字與特定的IP地址和端口綁定。
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
成功返回0,失敗返回-1。
sockfd 要分配地址信息(IP地址和端口號)的套接字文件描述符
myaddr 存有地址信息的結構體變量地址值
addrlen 第二個結構體變量的長度
其中的結構體原型 :
struct sockaddr_in
{
sa_family_t sin_family;//地址族
uint16_t sin_port;//16位tcp/udp端口號,以網絡字節序保存
struct in_addr sin_addr;//32位IP地址,以網絡字節序保存
char sin_zero[8];//不使用,必需爲0,
爲使結構體sockaddr_in的大小與sockaddr結構體保持一致而插入的成員
};
該結構體的初始化舉例:
struct sockaddr_in addr;
char * serv_ip = "211.217.168.13"; //聲明IP地址字符串
char * serv_port = "9190"; //聲明端口號字符串
memset(&addr, 0, sizeof(addr)); //結構體變量addr的所有成員置零
addr.sin_family = AF_INET; //指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); //基於字符串的IP地址初始化
addr.sin_port = htons(atoi(serv_port)); //基於字符串的端口號初始化
1.3 connect() 函數
connect() 函數由客戶端用來建立連接
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
1.4 listen()函數
listen()函數讓套接字進入被動監聽狀態。
int listen(int sockfd, int backlog);
1.5 accept()函數
accept() 函數當套接字處於監聽狀態時,可以通過 accept() 函數來接收客戶端請求。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
addr 保存發起連接請求的客戶端地址信息的變量地址值 .
函數調用成功時,accept函數內部將產生用於數據I/O的套接字,並返回其文件描述符,套接字自動創建並自動與發起連接的客戶端建立連接 。
1.6 send()與recv()函數
send() 函數發送數據,recv() 函數接受數據
int send(int sockfd, char *buf, int len, int flags);
int recv(int sockfd, char *buf, int len, int flags);
buf 爲要發送的數據的緩衝區地址,len 爲要發送的數據的字節數,flags 爲發送數據時的選項。
1.7 closesocket()函數關閉套接字,原型爲:
void closesocket(int sockfd);
2 服務端c/c++代碼
tcp_server.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#define MAXDATASIZE 100
#define PORT 8087
#define KEY 123
#define SIZE 1024
int main()
{
char buf[100];
memset(buf,0,100);
int server_sockfd,client_sockfd;
socklen_t server_len,client_len;
struct sockaddr_in server_sockaddr,client_sockaddr;
/*建立服務端套接字*/
server_sockfd = socket(AF_INET,SOCK_STREAM,0);
server_sockaddr.sin_family = AF_INET; //ipv4
server_sockaddr.sin_port = htons(PORT); //端口 #define PORT 8087
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); //任意地址
server_len = sizeof(server_sockaddr);
int on;
setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR,&on,sizeof(on));
/*bind 將套接字與IP地址和端口綁定*/
if(bind(server_sockfd, (struct sockaddr*)&server_sockaddr, server_len)==-1){
printf("bind error");
exit(1);
}
//監聽
if(listen(server_sockfd, 5)==-1){
printf("listen error");
exit(1);
}
client_len = sizeof(client_sockaddr);
pid_t ppid,pid;
while(1) {
printf("start\n");
//當套接字處於監聽狀態時,可以通過 accept() 函數來接收客戶端請求。
if((client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_sockaddr, &client_len)) == -1){
printf("connect error");
exit(1);
} else
{
printf("create connection successfully\n");
while(1){
//發送數據
int error = send(client_sockfd, "I am the server", strlen("I am the server"), 0);
if(error > 0 ){
printf("send : I am the server\n");
}
//接收數據
int numbytes = recv(client_sockfd, buf, MAXDATASIZE, 0);
if (numbytes > 0) {
buf[numbytes] = '\0';
printf("Received: %s\n",buf);
}
sleep(1);
}
}
printf("next\n");
}
return 0;
}
3 客戶端c/c++代碼
tcp_client.cpp
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_PORT 8087
#define MAXDATASIZE 100
#define SERVER_IP "127.0.0.1"
int main() {
int sockfd, numbytes;
char buf[MAXDATASIZE];
struct sockaddr_in server_addr;
printf("\n client initialization \n");
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
bzero(&(server_addr.sin_zero),sizeof(server_addr.sin_zero));
//連接
if (connect(sockfd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) == -1){
perror("connect error");
exit(1);
}
while(1) {
bzero(buf,MAXDATASIZE);
//接收消息
if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1){
perror("recv");
exit(1);
} else if (numbytes > 0) { //若成功接收
int len, bytes_sent;
buf[numbytes] = '\0';
printf("Received:%s\n",buf);
char msg[100];
strcpy(msg,"i am a client");
len = strlen(msg);
//sent to the server
if(send(sockfd, msg,len,0) == -1){
perror("send error");
}else{
printf("Send :i am a client\n");
}
}
else {
printf("soket end!\n");
break;
}
sleep(1);
}
close(sockfd);
return 0;
}
4 demo測試
將以上兩個cpp文件放在一個文件夾下,分別編譯
g++ -O3 tcp_client.cpp -o client
g++ -O3 tcp_server.cpp -o server
在兩個終端分別運行:
./server
./client
測試結果:
可見完成了雙向通信的demo。