背景
因為最近在研究網(wǎng)絡相關(guān)的東西耕腾,因此經(jīng)常要寫程序做實驗來驗證惕鼓。主要是TCP通信期揪,因此就寫了個簡單的基于TCP通信的小程序,方便以后要使用的時候能直接復用晦款,省的還要各種谷歌炎功、百度。
功能介紹
寫的很簡單缓溅,實現(xiàn)的就是客戶端讀取鍵盤輸入蛇损,發(fā)送給服務端,服務端打印出該輸入坛怪。
因為只研究TCP通信原理淤齐,就沒有再做其他的多線程并發(fā)之類的功能。
代碼
1袜匿、先看服務端代碼
/*服務端TCP程序一般流程
*1更啄、創(chuàng)建socket
*2、綁定端口和ip
*3居灯、監(jiān)聽socket
*4祭务、接收客戶端的請求
*5、從緩沖區(qū)中讀取數(shù)據(jù)
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 5000
#define IP "192.168.0.106"
#define BACKLOG 5
int main()
{
? ? int listen_fd;
? ? int accept_fd;
? ? char buf[1024] = {0};//讀寫緩沖區(qū)
? ? struct sockaddr_in server_addr;
? ? //需要獲取客戶端相關(guān)信息
? ? struct sockaddr_in client_addr;
? ? socklen_t client_len;
? ? client_len = sizeof(client_addr);
? ? //創(chuàng)建socket
? ? if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
? ? ? ? perror("socket");
? ? ? ? return 1;
? ? }
? ? //綁定端口和ip
? ? server_addr.sin_family = AF_INET;
? ? server_addr.sin_port = htons(PORT);
? ? server_addr.sin_addr.s_addr = inet_addr(IP);
? ? if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
? ? ? ? perror("bind");
? ? ? ? return 2;
? ? }
? ? //監(jiān)聽socket
? ? if (listen(listen_fd, BACKLOG) < 0) {
? ? ? ? perror("listen");
? ? ? ? return 3;
? ? }
? ? while(1) {
? ? ? ? //接收客戶端的請求
? ? ? ? if ((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len)) < 0) {
? ? ? ? ? ? perror("accept");
? ? ? ? ? ? return 4;
? ? ? ? }
? ? ? ? //打印客戶端信息
? ? ? ? printf("connected with ip: %s: port:%d\n",
? ? ? ? ? ? inet_ntop(AF_INET, &client_addr.sin_addr, buf, 1024), ntohs(client_addr.sin_port));
? ? ? ? while (1) {
? ? ? ? ? ? memset(buf, 0, sizeof(buf));
? ? ? ? ? ? //從緩沖區(qū)中讀取數(shù)據(jù)
? ? ? ? ? ? ssize_t size = read(accept_fd, buf, sizeof(buf) - 1);
? ? ? ? ? ? if (size > 0)
? ? ? ? ? ? ? ? printf("client send: %s\n", buf);
? ? ? ? ? ? else if (size == 0) {
? ? ? ? ? ? ? ? printf("read done!\n");
? ? ? ? ? ? ? ? close(accept_fd);
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? ? ? else {
? ? ? ? ? ? ? ? perror("read");
? ? ? ? ? ? ? ? close(accept_fd);
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }?
? ? ? ? }
? ? }
? ? close(listen_fd);
? ? return 0;
}
2怪嫌、然后是客戶端代碼
/*客戶端TCP程序一般流程
*1义锥、創(chuàng)建socket
*2、向服務端發(fā)起連接
*3岩灭、向緩沖區(qū)中寫入數(shù)據(jù)
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 5000
#define IP "192.168.0.106"
int main()
{
? ? int client_fd;
? ? char buf[1024] = {0};//讀寫緩沖區(qū)
? ? struct sockaddr_in client_addr;
? ? ssize_t size;
? ? //創(chuàng)建socket
? ? if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
? ? ? ? perror("socket");
? ? ? ? return 1;
? ? }
? ? //填充ip端口信息
? ? client_addr.sin_family = AF_INET;
? ? client_addr.sin_port = htons(PORT);
? ? client_addr.sin_addr.s_addr = inet_addr(IP);
? ? //向服務端發(fā)起連接
? ? if (connect(client_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) {
? ? ? ? perror("connect");
? ? ? ? return 2;
? ? }
? ? while(1) {
? ? ? ? memset(buf, 0, sizeof(buf));
? ? ? ? //從標準輸入獲取字符串
? ? ? ? if (!gets(buf)) {
? ? ? ? ? ? perror("gets");
? ? ? ? ? ? return 3;
? ? ? ? }
? ? ? ? if (strcmp(buf, "quit") == 0)
? ? ? ? ? ? break;
? ? ? ? if (write(client_fd, buf, strlen(buf)) != strlen(buf)) {
? ? ? ? ? ? perror("write");
? ? ? ? ? ? return 4;
? ? ? ? }
? ? }
? ? close(client_fd);
? ? return 0;
}
3缨该、最后是一個簡單的Makefile
all:
? ? gcc server.c -o server
? ? gcc client.c -o client
clean:
? ? rm -rf server client
編譯后程序就能跑起來了,使用netstat命令查看連接狀態(tài)如下:
客戶端退出后川背,服務端還是在監(jiān)聽新連接的到來贰拿。
探討
1、對于服務端代碼的accept函數(shù)
一般情況我們是將第二熄云、三個入?yún)⒅脼榭盏呐蚋硎疚覀儾魂P(guān)注客戶端信息,如下
accept_fd = accept(listen_fd, NULL, NULL);
此處我們?yōu)榱舜蛴】蛻舳薸p和端口缴允,因此傳入了我們定義的結(jié)構(gòu)體荚守,用于獲取客戶端信息珍德。
2、關(guān)于服務端監(jiān)聽ip矗漾,或者是說綁定的ip
一般我們服務端程序監(jiān)聽全網(wǎng)段ip锈候,即在調(diào)用bind函數(shù)時ip地址使用INADDR_ANY作為參數(shù),如下敞贡,
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
這兩種使用的區(qū)別我們可以通過netstat命令查看泵琳,對比如下闲勺,
上面的圖為設置了具體監(jiān)聽ip的程序乱凿,下面是使用INADDR_ANY為參數(shù)的程序。由此亡嫌,我們編程時就需要根據(jù)環(huán)境上是否有多個ip蛔垢,程序是否需要監(jiān)聽所有網(wǎng)卡的端口來確定我們函數(shù)的參數(shù)設置击孩。
3、客戶端程序一般不調(diào)用bind函數(shù)
在客戶端程序中我們一般不調(diào)用bind函數(shù)鹏漆,因為我們其實一般并不關(guān)心客戶端使用什么ip和什么端口和服務端通信巩梢,內(nèi)核會替你選擇一個未被占用的端口以及能和服務端通信的ip來發(fā)起連接。但是作為客戶端艺玲,我們是否可以使用bind函數(shù)呢括蝠?答案是肯定的“宀担考慮以下場景又跛,如果我們想要指定客戶端連接從哪個ip出去碍拆,使用哪個端口若治,這時候就必須使用bind函數(shù)了,這也就是bind函數(shù)的作用感混。
我們將上面客戶端的程序稍微修改一下端幼,增加bind操作。為了更好的看出bind的結(jié)果弧满,我在環(huán)境上配了兩個ip婆跑,讓客戶端和服務端各在一個ip上進行通信。我們配置客戶端使用5555號端口庭呜,綁定在ip:192.168.0.107上滑进,如代碼所示:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 5000
#define IP "192.168.0.106"
int main()
{
? ? int client_fd;
? ? char buf[1024] = {0};//讀寫緩沖區(qū)
? ? struct sockaddr_in client_addr;
? ? ssize_t size;
? ? //創(chuàng)建socket
? ? if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
? ? ? ? perror("socket");
? ? ? ? return 1;
? ? }
? ? /**************************************************************************/
? ? //客戶端使用bind函數(shù)綁定ip和端口
? ? struct sockaddr_in client_bind;
? ? client_bind.sin_family = AF_INET;
? ? client_bind.sin_port = htons(5555);
? ? client_bind.sin_addr.s_addr = inet_addr("192.168.0.107");
? ? if (bind(client_fd, (struct sockaddr*)&client_bind, sizeof(client_bind)) < 0) {
? ? ? ? ? ? perror("bind");
? ? ? ? ? ? return 5;
? ? }
? ? /**************************************************************************/
? ? //填充ip端口信息
? ? client_addr.sin_family = AF_INET;
? ? client_addr.sin_port = htons(PORT);
? ? client_addr.sin_addr.s_addr = inet_addr(IP);
? ? //向服務端發(fā)起連接
? ? if (connect(client_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) {
? ? ? ? perror("connect");
? ? ? ? return 2;
? ? }
? ? while(1) {
? ? ? ? memset(buf, 0, sizeof(buf));
? ? ? ? //從標準輸入獲取字符串
? ? ? ? if (!gets(buf)) {
? ? ? ? ? ? perror("gets");
? ? ? ? ? ? return 3;
? ? ? ? }
? ? ? ? if (strcmp(buf, "quit") == 0)
? ? ? ? ? ? break;
? ? ? ? if (write(client_fd, buf, strlen(buf)) != strlen(buf)) {
? ? ? ? ? ? perror("write");
? ? ? ? ? ? return 4;
? ? ? ? }
? ? }
? ? close(client_fd);
? ? return 0;
}
編譯運行后同樣使用netstat命令觀察連接狀態(tài),如下:
對比之前的截圖我們可以發(fā)現(xiàn)募谎,客戶端的ip和端口已經(jīng)變成我們設置的值扶关。所以說,bind在客戶端也是可以使用的数冬。