一、前言
手把手教你從0開始編寫TCP服務(wù)器程序,體驗(yàn)開局一塊磚,大廈全靠壘。
為了避免篇幅過長使讀者感到乏味,對【TCP服務(wù)器的開發(fā)】進(jìn)行分階段實(shí)現(xiàn),一步步進(jìn)行優(yōu)化升級。
二、需要使用到的API
2.1、socket()函數(shù)
函數(shù)原型:
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
這個函數(shù)建立一個協(xié)議族、協(xié)議類型、協(xié)議編號的socket文件描述符。如果函數(shù)調(diào)用成功,會返回一個標(biāo)識這個套接字的文件描述符,失敗的時候返回-1并設(shè)置了errno。
domain參數(shù)值含義:
名稱 | 含義 |
---|---|
PF_UNIX,PF_LOCAL | 本地通信 |
AF_INET,PF_INET | IPv4協(xié)議 |
PF_INET6 | IPv6協(xié)議 |
PF_NETLINK | 內(nèi)核用戶界面設(shè)備 |
PF_PACKET | 底層包訪問 |
type參數(shù)值含義:
名稱 | 含義 |
---|---|
SOCK_STREAM | TCP連接,提供序列化的、可靠的、雙向連接的字節(jié)流。支持帶外數(shù)據(jù)傳輸 |
SOCK_DGRAM | UDP連接 |
SOCK_SEQPACKET | 序列化包,提供一個序列化的、可靠的、雙向的數(shù)據(jù)傳輸通道,數(shù)據(jù)長度定常。每次調(diào)用讀系統(tǒng)調(diào)用時數(shù)據(jù)需要將全部數(shù)據(jù)讀出 |
:SOCK_PACKET | 專用類型 |
SOCK_RDM | 提供可靠的數(shù)據(jù)報文,不保證數(shù)據(jù)有序 |
SOCK_RAW | 提供原始網(wǎng)絡(luò)協(xié)議訪問 |
protocol參數(shù)含義:
通常某協(xié)議中只有一種特定類型,這樣protocol參數(shù)僅能設(shè)置為0;如果協(xié)議有多種特定的類型,就需要設(shè)置這個參數(shù)來選擇特定的類型。
2.2、bind()函數(shù)
函數(shù)原型:
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
參數(shù)說明:
- 第1個參數(shù)sockfd是用socket()函數(shù)創(chuàng)建的文件描述符。
- 第2個參數(shù)my_addr是指向一個結(jié)構(gòu)為sockaddr參數(shù)的指針,sockaddr中包含了地址、端口和IP地址的信息。
- 第3個參數(shù)addrlen是my_addr結(jié)構(gòu)的長度,可以設(shè)置成sizeof(struct sockaddr)。
bind()函數(shù)的返回值為0時表示綁定成功,-1表示綁定失敗并設(shè)置了errno。
2.3、listen()函數(shù)
函數(shù)原型:
#include<sys/socket.h>
int listen(int sockfd, int backlog);
參數(shù)說明:
- 第1個參數(shù)sockfd是用socket()函數(shù)創(chuàng)建的文件描述符。
- 第2個參數(shù)backlog規(guī)定了內(nèi)核應(yīng)該為相應(yīng)套接字排隊(duì)的最大連接個數(shù)。
2.4、accept()函數(shù)
函數(shù)原型:
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
參數(shù)說明:
- sockefd:套接字描述符,該套接字在listen() 后監(jiān)聽連接。
- addr:(可選)指針。指向一個緩沖區(qū),其中接收為通訊層所知的連接實(shí)體的地址。Addr參數(shù)的實(shí)際格式由套接口創(chuàng)建時所產(chǎn)生的地址族確定。
- addrlen:(可選)指針。輸入?yún)?shù),配合addr一起使用,指向存有addr地址長度的整形數(shù)。
2.5、recv()函數(shù)
函數(shù)原型:
#include<sys/types.h>
#include<sys/socket.h>
int recv( int fd, char *buf, int len, int flags);
參數(shù)說明:
- 第一個參數(shù)指定接收端套接字描述符;
- 第二個參數(shù)指明一個緩沖區(qū),該緩沖區(qū)用來存放recv函數(shù)接收到的數(shù)據(jù);
- 第三個參數(shù)指明buf的長度;
- 第四個參數(shù)一般置0。
返回值:
- 返回大于0的數(shù),表示介紹到的數(shù)據(jù)大小。
- 返回0,表示連接斷開。
- 返回-1,表示接受數(shù)據(jù)錯誤。
2.6、send()函數(shù)
函數(shù)原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
參數(shù)說明:
- sockfd:向套接字中發(fā)送數(shù)據(jù)
- buf:要發(fā)送的數(shù)據(jù)的首地址
- len:要發(fā)送的數(shù)據(jù)的字節(jié)
- int flags:設(shè)置為MSG_DONTWAITMSG 時 表示非阻塞,設(shè)置為0時 功能和write一樣。
返回值:成功返回實(shí)際發(fā)送的字節(jié)數(shù),失敗返回 -1并設(shè)置了errno。
2.7、strerror()函數(shù)
strerror()
函數(shù)返回一個指向字符串的指針,該字符串描述參數(shù)errnum中傳遞的錯誤代碼,可能使用當(dāng)前語言環(huán)境的LC_MESSAGES部分來選擇適當(dāng)?shù)恼Z言。(例如,如果errnum為EINVAL,則返回的描述將為“無效參數(shù)”。)應(yīng)用程序不能修改此字符串,但可以通過隨后調(diào)用strerror()
或strerror_l()
來修改。任何其他庫函數(shù),包括perror()
,都不會修改此字符串。
函數(shù)原型:
#include <string.h>
char *strerror(int errnum);
int strerror_r(int errnum, char *buf, size_t buflen);
/* XSI-compliant */
char *strerror_r(int errnum, char *buf, size_t buflen);
/* GNU-specific */
char *strerror_l(int errnum, locale_t locale);
三、實(shí)現(xiàn)步驟
一對一服務(wù)器設(shè)計(jì):
(1)創(chuàng)建socket。
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
return SOCKET_CREATE_FAILED;
}
(2)綁定地址。
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);
if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_BIND_FAILED;
}
(3)設(shè)置監(jiān)聽。
if(-1==listen(listenfd,BLOCK_SIZE)){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_LISTEN_FAILED;
}
(4)接收連接。
struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_ACCEPT_FAILED;
}
(5)接收數(shù)據(jù)。
char buf[BUFFER_LENGTH]={0};
ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {
printf("connection dropped\n");
}
printf("recv --> %s\n",buf);
(6)發(fā)送數(shù)據(jù)。
if(-1==send(clientfd,buf,ret,0))
{
printf("errno = %d, %s\n",errno,strerror(errno));
}
(7)關(guān)閉文件描述符。
close(clientfd);
close(listenfd);
四、完整代碼
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#define LISTEN_PORT 8888
#define BLOCK_SIZE 10
#define BUFFER_LENGTH 1024
enum ERROR_CODE{
SOCKET_CREATE_FAILED=-1,
SOCKET_BIND_FAILED=-2,
SOCKET_LISTEN_FAILED=-3,
SOCKET_ACCEPT_FAILED=-4
};
int main(int argc,char **argv)
{
// 1.
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
return SOCKET_CREATE_FAILED;
}
// 2.
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);
if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_BIND_FAILED;
}
// 3.
if(-1==listen(listenfd,BLOCK_SIZE)){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_LISTEN_FAILED;
}
// 4.
struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_ACCEPT_FAILED;
}
printf("client fd = %d\n",clientfd);
int ret=1;
while(ret>0){
// 5.
char buf[BUFFER_LENGTH]={0};
ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {
printf("connection dropped\n");
break;
}
printf("recv --> %s\n",buf);
if(-1==send(clientfd,buf,ret,0))
{
printf("errno = %d, %s\n",errno,strerror(errno));
}
}
close(clientfd);
close(listenfd);
return 0;
}
編譯命令:
gcc -o server server.c
五、TCP客戶端
5.1、自己實(shí)現(xiàn)一個TCP客戶端
自己實(shí)現(xiàn)一個TCP客戶端連接TCP服務(wù)器的代碼:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define BUFFER_LENGTH 1024
enum ERROR_CODE{
SOCKET_CREATE_FAILED=-1,
SOCKET_CONN_FAILED=-2,
SOCKET_LISTEN_FAILED=-3,
SOCKET_ACCEPT_FAILED=-4
};
int main(int argc,char** argv)
{
if(argc<3)
{
printf("Please enter the server IP and port.");
return 0;
}
printf("connect to %s, port=%s\n",argv[1],argv[2]);
int connfd=socket(AF_INET,SOCK_STREAM,0);
if(connfd==-1)
{
printf("errno = %d, %s\n",errno,strerror(errno));
return SOCKET_CREATE_FAILED;
}
struct sockaddr_in serv;
serv.sin_family=AF_INET;
serv.sin_addr.s_addr=inet_addr(argv[1]);
serv.sin_port=htons(atoi(argv[2]));
socklen_t len=sizeof(serv);
int rwfd=connect(connfd,(struct sockaddr*)&serv,len);
if(rwfd==-1)
{
printf("errno = %d, %s\n",errno,strerror(errno));
close(rwfd);
return SOCKET_CONN_FAILED;
}
int ret=1;
while(ret>0)
{
char buf[BUFFER_LENGTH]={0};
printf("Please enter the string to send:\n");
scanf("%s",buf);
send(connfd,buf,strlen(buf),0);
memset(buf,0,BUFFER_LENGTH);
printf("recv:\n");
ret=recv(connfd,buf,BUFFER_LENGTH,0);
printf("%s\n",buf);
}
close(rwfd);
return 0;
}
編譯:
gcc -o client client.c
5.2、Windows下可以使用NetAssist的網(wǎng)絡(luò)助手工具
下載地址:http://old.tpyboard.com/downloads/NetAssist.exe文章來源:http://www.zghlxwxcb.cn/news/detail-773513.html
小結(jié)
至此,我們實(shí)現(xiàn)了一個一對一的服務(wù)器連接,這階段的TCP服務(wù)器代碼只能接收一個客戶端接入,如果客戶端斷開了就會直接退出。重點(diǎn)是掌握開發(fā)TCP服務(wù)器的基本流程,下一章節(jié)將介紹在此基礎(chǔ)上進(jìn)行升級,實(shí)現(xiàn)可以接受多個客戶端的同時接入。文章來源地址http://www.zghlxwxcb.cn/news/detail-773513.html
到了這里,關(guān)于【TCP服務(wù)器的演變過程】編寫第一個TCP服務(wù)器:實(shí)現(xiàn)一對一的連接通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!