ソケット(ソケット)、ソケット。コンピュータ内で実行される 2 つのプログラムはソケットを介してチャネルを確立し、データはそのチャネル内で送信されます。
ソケットには複雑な TCP/IP プロトコルが隠されており、プログラマにとっては、ソケット関連の機能をうまく使えば、ネットワーク通信を完了できます。
ソケットは、ストリームとデータグラムという 2 つの通信メカニズムを提供します。
ストリーム ソケットは、整然とした信頼性の高い双方向バイト ストリーム チャネルである TCP プロトコルに基づいており、送信されたデータは失われたり、繰り返されたり、乱れたりすることはありません。
データグラム ソケットは UDP プロトコルに基づいており、失われたり混乱する可能性がある接続を確立および維持する必要はありません。UDP は信頼できるプロトコルではなく、データの長さに制限がありますが、効率は比較的高いです。
単純なソケット通信プロセス:
サーバープログラム:
/*
* 程序名: server.cpp 用于演示 socket 通信的服务端
*/
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
std::cout << "Using:./server port" << std::endl;
std::cout << "Example:./server 5005" << std::endl;;
return -1;
}
// 第1步:创建服务端的 socket
int listenfd = 0;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
return -1;
}
// 第2步:把服务端有用于通信的地址和端口绑定到 socket 上。
//struct sockaddr_in serveraddr; // 服务端地址信息的数据结构
sockaddr_in* serveraddr = new sockaddr_in;
serveraddr->sin_family = AF_INET; // 协议族,在 socket 编程中只能是 AF_INET
serveraddr->sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址
//serveraddr.sin_addr.s_addr = inet_addr("192.168.199.134") // 固定 ip 地址
serveraddr->sin_port = htons(atoi(argv[1])); // 指定通信端口
if (bind(listenfd, (struct sockaddr *) serveraddr, sizeof(*serveraddr)) != 0) {
perror("bind");
close(listenfd);
return -1;
}
// 第3步:把 socket 设置为监听模式
if (listen(listenfd, 5) != 0) {
perror("listen");
close(listenfd);
return -1;
}
// 第4步:接受客户端的连接
int clientfd; // 客户端的 socket
int socklen = sizeof(struct sockaddr_in); // struct sockaddr-in 大小
struct sockaddr_in clientaddr; // 客户端的地址信息
clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, (socklen_t*)&socklen);
std::cout << "客户端" << inet_ntoa(clientaddr.sin_addr) << "已连接。" << std::endl;
// 第5步:与客户端通信,接受客户端发过来的报文后,回复OK
char buffer[1024];
while (1) {
int iret;
memset(buffer, 0, sizeof(buffer));
if ((iret = recv(clientfd, buffer, sizeof(buffer), 0)) <= 0) {
std::cout << "iret = " << iret << std::endl;
break;
}
std::cout << "接收: " << buffer << std::endl;
strcpy(buffer, "OK");
if ((iret = send(clientfd, buffer, strlen(buffer), 0)) <= 0) {
perror("send");
break;
}
std::cout << "发送" << buffer << std::endl;
}
// 第6步: 关闭 socket, 释放资源
close(listenfd);
close(clientfd);
}
クライアントプログラム:
/*
* 程序名:client.cpp 为 socket 客户端
*/
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cout << "using: ./client ip port" <<std::endl;
std::cout << "Example:./client 127.0.0.1 5005" << std::endl;
return -1;
}
// 第1步:创建客户端的socket
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
return -1;
}
// 第2步:想服务器发起连接请求
struct hostent* h;
if ((h = gethostbyname(argv[1])) == 0) { // 指定服务器的 ip 地址
std::cout << "gethostbyname failed" << std::endl;
close(sockfd);
return -1;
}
sockaddr_in* servaddr = new sockaddr_in;
servaddr->sin_family = AF_INET;
servaddr->sin_port = htons(atoi(argv[2])); // 指定服务器的通信端口
memcpy(&servaddr->sin_addr, h->h_addr, h->h_length);
if (connect(sockfd, (struct sockaddr*)servaddr, sizeof(*servaddr)) != 0) { // 像服务端发起
连接
perror("connect");
close(sockfd);
return -1;
}
char buffer[1024];
// 第3步:与服务器通信,发送一个报文后等待回复,然后再发下一个报文
for (int i = 0 ; i < 10; i++) {
int iret;
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "这是第%d个超级女生, 编号为%03d", i + 1, i + 1);
if ((iret = send(sockfd, buffer, strlen(buffer), 0)) <= 0) {
perror("send");
break;
}
std::cout << "发送:" << buffer << std::endl;
memset(buffer, 0, sizeof(buffer));
if ((iret = recv(sockfd, buffer, sizeof(buffer), 0)) <= 0) {
std::cout << "iret=" << iret << std::endl;
break;
}
std::cout << "接受: " << buffer << std::endl;
}
// 第4步:关闭 socket,释放资源
close(sockfd);
}
プログラムを作成した後、g++ を使用してコンパイルします。 g++ -o 実行ファイル名 (server) プログラム名 (server.cpp)
サーバーの実行結果:
クライアントの実行結果:
プログラムの一般的な理解:
(1)ソケット()
int socket(int domain, int type, int protocol);
UNIX システムでは、すべてがファイルです。socket() 関数の戻り値は、本質的には整数であるファイル記述子です。
1 つのスレッドで同時にオープンできるファイルの数を制限します。ulimit -a と入力します。
open files (-n) 1024
したがって、ソケットの最大値は 1023 (0 から開始) となり、この値は調整可能です。
(2) sockaddr_in 構造体
//struct sockaddr_in serveraddr; // 服务端地址信息的数据结构
サーバーアドレス情報を保存するために使用される定義された構造体。
ipアドレスには、ipアドレスを指定する方法と任意のipアドレスを指定する方法があり、任意のipアドレスを使用する方法も数多くあります。
(3)送信()
send 関数はソケット経由でピア サーバーにデータを送信するために使用され、サーバーとクライアントの両方が send 関数を使用して TCP 接続の他端にデータを送信します。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
この関数は送信された文字数を返します。エラーが発生すると、-1 が返され、返されたエラー <= 0 は、通信リンクが使用できなくなったことを示します。
(4)recv()
recv 関数は、ピアソケットによって送信されたデータを受信するために使用されます。サーバーとクライアントは両方とも、recv 関数を使用して、TCP の相手側から送信されたデータを受信します。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ソケットの相手側がデータを送信しない場合、recv 関数は待機します。相手側がデータを送信すると、関数は受信した文字数を返します。エラーが発生すると、-1 が返され、返されたエラー <= 0 は、通信リンクが使用できなくなったことを示します。
(5) サーバー側にはソケットが 2 つあります
サーバーには 2 つのソケットがあり、1 つは監視に使用され、もう 1 つはクライアントが正常に接続された後にクライアントとメッセージを送受信するために accept 関数によって作成されるソケットです。
clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, (socklen_t*)&socklen);
(6) プログラムが終了する前にソケットを閉じます。
ソケットはシステム リソースであり、オペレーティング システムによって開かれるソケットの数は制限されており、プログラムが終了する前に開いているソケットを閉じる必要があります。