????????經(jīng)過前幾篇的介紹,本文我們將進行編程實戰(zhàn),實現(xiàn)一個簡單地服務器和客戶端應用程序。
編寫服務器程序
????????編寫服務器應用程序的流程如下:
????????①、調(diào)用 socket()函數(shù)打開套接字,得到套接字描述符;
????????②、調(diào)用 bind()函數(shù)將套接字與 IP 地址、端口號進行綁定;
????????③、調(diào)用 listen()函數(shù)讓服務器進程進入監(jiān)聽狀態(tài);
????????④、調(diào)用 accept()函數(shù)獲取客戶端的連接請求并建立連接;
????????⑤、調(diào)用 read()/recv()、write()/send() 與客戶端進行通信;
????????⑥、調(diào)用 close()關(guān)閉套接字。
????????下面,我們就根據(jù)上面列舉的步驟來編寫一個簡單的服務器應用程序,代碼如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define SERVER_PORT 8888 //端口號不能發(fā)生沖突,不常用的端口號通常大于5000
int main(void){
struct sockaddr_in server_addr = {0};
struct sockaddr_in client_addr = {0};
char ip_str[20] = {0};
int sockfd, connfd;
int addrlen = sizeof(client_addr);
char recvbuf[512];
int ret;
/* 打開套接字,得到套接字描述符 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (0 > sockfd) {
perror("socket error");
exit(EXIT_FAILURE);
}
/* 將套接字與指定端口號進行綁定 */
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (0 > ret) {
perror("bind error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 使服務器進入監(jiān)聽狀態(tài) */
ret = listen(sockfd, 50);
if (0 > ret) {
perror("listen error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 阻塞等待客戶端連接 */
connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
if (0 > connfd) {
perror("accept error");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("有客戶端接入...\n");
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
printf("客戶端主機的IP地址: %s\n", ip_str);
printf("客戶端進程的端口號: %d\n", client_addr.sin_port);
/* 接收客戶端發(fā)送過來的數(shù)據(jù) */
for ( ; ; ) {
// 接收緩沖區(qū)清零
memset(recvbuf, 0x0, sizeof(recvbuf));
// 讀數(shù)據(jù)
ret = recv(connfd, recvbuf, sizeof(recvbuf), 0);
if(0 >= ret) {
perror("recv error");
close(connfd);
break;
}
// 將讀取到的數(shù)據(jù)以字符串形式打印出來
printf("from client: %s\n", recvbuf);
// 如果讀取到"exit"則關(guān)閉套接字退出程序
if (0 == strncmp("exit", recvbuf, 4)) {
printf("server exit...\n");
close(connfd);
break;
}
}
/* 關(guān)閉套接字 */
close(sockfd);
exit(EXIT_SUCCESS);
}
????????以上我們實現(xiàn)了一個非常簡單地服務器應用程序,根據(jù)上面列舉的步驟完成了這個示例代碼,最終的功能是,當客戶端連接到服務器之后,客戶端會向服務器(也就是本程序)發(fā)送數(shù)據(jù),在我們服務器應用程序中會讀取客戶端發(fā)送的數(shù)據(jù)并將其打印出來,就是這么簡單的一個功能。
????????SERVER_PORT 宏指定了本服務器綁定的端口號,這里我們將端口號設(shè)置為 8888,端口不能與其它服務器的端口號發(fā)生沖突,不常用的端口號通常大于 5000。
????????另外,bind綁定的IP地址是 INADDR_ANY,即表示所有發(fā)送到服務器的這個端口,不管是哪個網(wǎng)卡/哪個IP地址接收到的數(shù)據(jù),都由這個服務端進程進行處理。
編寫客戶端程序
????????接下來我們再編寫一個簡單地客戶端應用程序,客戶端的功能是連接上小節(jié)所實現(xiàn)的服務器,連接成功之后向服務器發(fā)送數(shù)據(jù),發(fā)送的數(shù)據(jù)由用戶輸入。示例代碼如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define SERVER_PORT 8888 //服務器的端口號
#define SERVER_IP "192.168.1.150" //服務器的IP地址
int main(void){
struct sockaddr_in server_addr = {0};
char buf[512];
int sockfd;
int ret;
/* 打開套接字,得到套接字描述符 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (0 > sockfd) {
perror("socket error");
exit(EXIT_FAILURE);
}
/* 調(diào)用connect連接遠端服務器 */
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT); //端口號
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);//IP地址
ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (0 > ret) {
perror("connect error");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("服務器連接成功...\n\n");
/* 向服務器發(fā)送數(shù)據(jù) */
for ( ; ; ) {
// 清理緩沖區(qū)
memset(buf, 0x0, sizeof(buf));
// 接收用戶輸入的字符串數(shù)據(jù)
printf("Please enter a string: ");
fgets(buf, sizeof(buf), stdin);
// 將用戶輸入的數(shù)據(jù)發(fā)送給服務器
ret = send(sockfd, buf, strlen(buf), 0);
if(0 > ret){
perror("send error");
break;
}
//輸入了"exit",退出循環(huán)
if(0 == strncmp(buf, "exit", 4))
break;
}
close(sockfd);
exit(EXIT_SUCCESS);
}
????????代碼不再說明!需要注意的是 SERVER_IP 和 SERVER_PORT 指的是服務器的 IP 地址和端口號,服務器的 IP 地址根據(jù)實際情況進行設(shè)置,服務器應用程序服務器端代碼中我們綁定的端口號為 8888,所以在客戶端應用程序中我們也需要指定 SERVER_PORT 為 8888。
編譯測試
????????這里筆者將服務器程序運行在開發(fā)板上,而將客戶端應用程序運行在 Ubuntu 系統(tǒng),當然你也可以將客戶端和服務器程序都運行在開發(fā)板或 Ubuntu 系統(tǒng),這都是沒問題的。
????????首先編譯服務器應用程序和客戶端應用程序:
?????????編譯得到 client 和 server 可執(zhí)行文件,server 可執(zhí)行文件在開發(fā)板上運行,client 可執(zhí)行文件在 PC 端 Ubuntu 系統(tǒng)下運行。將 server 可執(zhí)行文件拷貝到開發(fā)板目錄下,如下所示:
?????????在開發(fā)板執(zhí)行 server:
????????接著在 Ubuntu 系統(tǒng)執(zhí)行客戶端程序:
?????????客戶端運行之后將會去連接遠端服務器,連接成功便會打印出信息“服務器連接成功...”,此時服務器也會監(jiān)測到客戶端連接,會打印相應的信息,如下所示:
????????接下來我們便可以在客戶端處輸入字符串,客戶端程序會將我們輸入的字符串信息發(fā)送給服務器,服務器接收到之后將其打印出來,如下所示:
Tips : 如果連接出現(xiàn)問題如“connect error: No route to host”,可以參考這篇問題記錄開發(fā)板和虛擬機socket報錯“connect error: No route to host”。
?總結(jié)文章來源:http://www.zghlxwxcb.cn/news/detail-772169.html
????????到此,socket編程的內(nèi)容就告一段落,內(nèi)容講得非常淺,目的其實并不是讓大家學會網(wǎng)絡編程,旨在以引導大家入門為主,讓大家對 socket 網(wǎng)絡編程有一個基本的了解和認識。望諸君常學常新、學思踐悟。?文章來源地址http://www.zghlxwxcb.cn/news/detail-772169.html
到了這里,關(guān)于Linux下網(wǎng)絡編程(3)——socket編程實戰(zhàn),如何構(gòu)建一個服務器和客戶端連接的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!