江蘇網(wǎng)站開發(fā)建設(shè)百度客服在線咨詢?nèi)斯し?wù)
網(wǎng)絡(luò)的基本概念
TCP/IP協(xié)議概述
OSI和TCP/IP模型
socket(套接字)
創(chuàng)建socket
字節(jié)序
字節(jié)序轉(zhuǎn)換函數(shù)
通用地址結(jié)構(gòu)
因特網(wǎng)地址結(jié)構(gòu)
IPV4地址族和字符地址間的轉(zhuǎn)換(點(diǎn)分十進(jìn)制->網(wǎng)絡(luò)字節(jié)序)
填寫IPV4地址族結(jié)構(gòu)案例
掌握TCP協(xié)議網(wǎng)絡(luò)基礎(chǔ)編程
相關(guān)函數(shù)
特殊bind地址
案例1:time
-
先啟動(dòng)一個(gè)服務(wù)端,客戶鏈接上來之后,服務(wù)端返回一個(gè)系統(tǒng)時(shí)間
-
time_tcp_server.c
#include <stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>int sockfd;void sig_handler(int signo)
{if(signo==SIGINT){printf("server close\n");/*步驟6:* 關(guān)閉socket* */close(sockfd);exit(1);}
}/*輸出連接上來的客戶端相關(guān)信息* */
void out_addr(struct sockaddr_in *clientaddr)
{// 將端口從網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換成主機(jī)字節(jié)序int port = ntohs(clientaddr->sin_port);char ip[16];memset(ip,0,sizeof(ip));// 網(wǎng)絡(luò)字節(jié)序-> 點(diǎn)分十進(jìn)制inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));printf("client:%s(%d) connected \n",ip,port);
}
void do_service(int fd)
{// 獲得系統(tǒng)時(shí)間long t = time(0);char *s = ctime(&t);size_t size = strlen(s)*sizeof(char);// 將服務(wù)器端獲得的系統(tǒng)時(shí)間寫到客戶端if(write(fd,s,size)!=size){perror("write error");}
}
int main(int argc,char *argv[])
{// 參數(shù):服務(wù)器端綁定的端口if(argc<2){printf("usage :%s #port\n",argv[0]);exit(0);}// 綁定一個(gè)信號(hào),ctrl+c 終止服務(wù)端if(signal(SIGINT,sig_handler)==SIG_ERR){perror("signal sigint error");exit(1);}/* * 步驟1. 創(chuàng)建socket,套接字socket創(chuàng)建在內(nèi)核,是一個(gè)結(jié)構(gòu)體* AF_INET : IPV4* SOCK_STREAM:TCP協(xié)議*/sockfd = socket(AF_INET,SOCK_STREAM,0);/** 步驟2:調(diào)用bind函數(shù)將socket和地址(ip和port)進(jìn)行綁定 * */// 專用地址,綁定的時(shí)候在強(qiáng)轉(zhuǎn)為通用地址struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET; // IPV4serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主機(jī)字節(jié)序->網(wǎng)絡(luò)字節(jié)序serveraddr.sin_addr.s_addr = INADDR_ANY;//響應(yīng)所有請(qǐng)求if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("bind error");exit(1);}/**步驟3:調(diào)用listen函數(shù)啟動(dòng)監(jiān)聽(指定port監(jiān)聽)通知系統(tǒng)去接受來自客戶端的連接請(qǐng)求將接受到的客戶端連接請(qǐng)求放置到對(duì)應(yīng)的隊(duì)列中第二個(gè)參數(shù):10:隊(duì)列的長(zhǎng)度* */if(listen(sockfd,10)<0){perror("listen error");exit(1);}/**步驟4:調(diào)用accept函數(shù),從隊(duì)列中獲得一個(gè)客戶端的請(qǐng)求連接* 并返回一個(gè)新的socket描述符*注意:若沒有客戶端連接,調(diào)用此函數(shù)后會(huì)阻塞直到獲得一個(gè)客戶端的連接* */struct sockaddr_in clientaddr;socklen_t clientaddr_len = sizeof(clientaddr);while(1){// 第二個(gè)參數(shù)可以設(shè)置為NULL,如果不想知道客戶端的信息的話int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);if(fd<0){perror("accept error");continue;}/**步驟5:調(diào)用IO函數(shù)和客戶端進(jìn)行雙向的通信*/out_addr(&clientaddr);do_service(fd);/*步驟5:* 關(guān)閉 socket* */


- time_tcp_client.c```c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>int main(int argc,char *argv[])
{// 參數(shù):服務(wù)器端綁定的端口if(argc<2){printf("usage :%s #port\n",argv[0]);exit(0);}/* * 步驟1. 創(chuàng)建socket,套接字socket創(chuàng)建在內(nèi)核,是一個(gè)結(jié)構(gòu)體* AF_INET : IPV4* SOCK_STREAM:TCP協(xié)議*/int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){perror("sockek error");exit(1);}/** 步驟2:創(chuàng)建socket* */struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET; // IPV4serveraddr.sin_port = htons(atoi(argv[2]));// 字符串-> 整型,主機(jī)字節(jié)序->網(wǎng)絡(luò)字節(jié)序inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);if(connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("connect error");exit(1);}// 步驟3:調(diào)用IO函數(shù),和服務(wù)器進(jìn)行雙向通訊char buffer[1024];memset(buffer,0,sizeof(buffer));size_t size;if((size=read(sockfd,buffer,sizeof(buffer)))<0){perror("read error");}if(write(STDOUT_FILENO,buffer,size)!=size){perror("write error");}// 步驟4:關(guān)閉socketclose(sockfd);return 0;
}
自定義協(xié)議
- msg.h
#ifndef __MSG_H__
#define __MSG_H__
#include<sys/types.h>
typedef struct{// 協(xié)議頭部char head[10];// 驗(yàn)證碼char checknum;//協(xié)議體部char buff[512] ;//數(shù)據(jù)
}MSG;
/** 發(fā)送一個(gè)基于自定義協(xié)議的message,發(fā)送的數(shù)據(jù)存放在buff中* */
extern int write_msg(int sockfd,char *buff,size_t len);
/*讀取一個(gè)基于自定義協(xié)議的message。讀取的數(shù)據(jù)存放在buff中* */
extern int read_msg(int sockfd,char *buff,size_t len);#endif
- msg.c
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<sys/types.h>
#include"msg.h"// 計(jì)算校驗(yàn)碼
static unsigned char msg_check(Msg *message)
{unsigned char s = 0;for(int i=0;i<sizeof(message->head);i++){s+=message->head[i];}for(int i=0;i<sizeof(message->buff);i++){s+=message->buff[i];}return s;
}/** 發(fā)送一個(gè)基于自定義協(xié)議的message,發(fā)送的數(shù)據(jù)存放在buff中* */int write_msg(int sockfd,char *buff,size_t len)
{Msg message;memset(&message,0,sizeof(message));strcpy(message.head,"iotek2025");memcpy(message.buff,buff,len);message.checknum = msg_check(&message);if(write(sockfd,&message,sizeof(message))!=sizeof(message)){return -1;}
}/*讀取一個(gè)基于自定義協(xié)議的message。讀取的數(shù)據(jù)存放在buff中* */
int read_msg(int sockfd,char *buff,size_t len)
{Msg message;memset(&message,0,sizeof(message));size_t size;if((size=read(sockfd,&message,sizeof(message)))<0){return -1;}else if(size==0){return 0;}// j進(jìn)行校驗(yàn)碼的驗(yàn)證unsigned char s =msg_check(&message);if((s==(unsigned char)message.checknum) && (!strcmp("iotek2025",message.head))){memcpy(buff,message.buff,len);return sizeof(message);}return -1;
}
- 在服務(wù)端的do_service中增加
void do_service(int fd)
{
//----------------------------------------size_t len;char buff[20];if((len=read(fd,buff,20))<0) // 注意由于客戶端沒有給服務(wù)端寫數(shù)據(jù),所以會(huì)在此阻塞{perror("do_service server read error");}//----------------------------------------// 獲得系統(tǒng)時(shí)間long t = time(0);char *s = ctime(&t);size_t size = strlen(s)*sizeof(char);// 將服務(wù)器端獲得的系統(tǒng)時(shí)間寫到客戶端if(write(fd,s,size)!=size){perror("write error");}
}
服務(wù)器并發(fā)性處理
多進(jìn)程模型
- echo_tcp_server
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include"msg.h"
int sockfd;void sig_handler(int signo)
{if(signo==SIGINT){printf("server close\n");/*步驟6:* 關(guān)閉socket* */close(sockfd);exit(1);}if(signo==SIGCHLD){printf("child process dead.....\n");wait(0);}
}/*輸出連接上來的客戶端相關(guān)信息* */
void out_addr(struct sockaddr_in *clientaddr)
{// 將端口從網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換成主機(jī)字節(jié)序int port = ntohs(clientaddr->sin_port);char ip[16];memset(ip,0,sizeof(ip));// 網(wǎng)絡(luò)字節(jié)序-> 點(diǎn)分十進(jìn)制inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));printf("client:%s(%d) connected \n",ip,port);
}
void do_service(int fd)
{// 和客戶端進(jìn)行讀寫操作(雙向通信)char buff[512];while(1){memset(buff,0,sizeof(buff));printf("start read and write....\n");size_t size;if((size=read_msg(fd,buff,sizeof(buff)))<0){perror("protocal error");break;}else if(size==0){break; // 寫端突然掛了}else{printf("%s\n",buff);if(write_msg(fd,buff,sizeof(buff))<0){if(errno==EPIPE)// 讀端突然關(guān)閉時(shí),類似于管道通信{break;}perror("protocal error");}}}
}
int main(int argc,char *argv[])
{// 參數(shù):服務(wù)器端綁定的端口if(argc<2){printf("usage :%s #port\n",argv[0]);exit(0);}// 綁定一個(gè)信號(hào),ctrl+c 終止服務(wù)端if(signal(SIGINT,sig_handler)==SIG_ERR){perror("signal sigint error");exit(1);}if(signal(SIGCHLD,sig_handler)==SIG_ERR)// 等子進(jìn)程終止發(fā)送的信號(hào){perror("signal sigchild error");exit(1);}/* * 步驟1. 創(chuàng)建socket,套接字socket創(chuàng)建在內(nèi)核,是一個(gè)結(jié)構(gòu)體* AF_INET : IPV4* SOCK_STREAM:TCP協(xié)議*/sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){perror("sockek error");exit(1);}/** 步驟2:調(diào)用bind函數(shù)將socket和地址(ip和port)進(jìn)行綁定 * */// 專用地址,綁定的時(shí)候在強(qiáng)轉(zhuǎn)為通用地址struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET; // IPV4serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主機(jī)字節(jié)序->網(wǎng)絡(luò)字節(jié)序serveraddr.sin_addr.s_addr = INADDR_ANY;//響應(yīng)所有請(qǐng)求if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("bind error");exit(1);}/**步驟3:調(diào)用listen函數(shù)啟動(dòng)監(jiān)聽(指定port監(jiān)聽)通知系統(tǒng)去接受來自客戶端的連接請(qǐng)求將接受到的客戶端連接請(qǐng)求放置到對(duì)應(yīng)的隊(duì)列中第二個(gè)參數(shù):10:隊(duì)列的長(zhǎng)度* */if(listen(sockfd,10)<0){perror("listen error");exit(1);}/**步驟4:調(diào)用accept函數(shù),從隊(duì)列中獲得一個(gè)客戶端的請(qǐng)求連接* 并返回一個(gè)新的socket描述符*注意:若沒有客戶端連接,調(diào)用此函數(shù)后會(huì)阻塞直到獲得一個(gè)客戶端的連接* */struct sockaddr_in clientaddr;socklen_t clientaddr_len = sizeof(clientaddr);while(1){// 第二個(gè)參數(shù)可以設(shè)置為NULL,如果不想知道客戶端的信息的話int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);if(fd<0){perror("accept error");continue;}/**步驟5:啟動(dòng)子進(jìn)程調(diào)用IO函數(shù) */pid_t pid = fork();if(pid<0){continue;}else if(pid==0){out_addr(&clientaddr);do_service(fd);/*步驟6:* 關(guān)閉 socket* */close(fd); /// 子進(jìn)程會(huì)復(fù)制父進(jìn)程的fd}else{close(fd); }}return 0;
}
- echo_tcp_client.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include"msg.h"int main(int argc,char *argv[])
{// 參數(shù):服務(wù)器端綁定的端口if(argc<2){printf("usage :%s #port\n",argv[0]);exit(0);}/* * 步驟1. 創(chuàng)建socket,套接字socket創(chuàng)建在內(nèi)核,是一個(gè)結(jié)構(gòu)體* AF_INET : IPV4* SOCK_STREAM:TCP協(xié)議*/int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){perror("sockek error");exit(1);}/** 步驟2:創(chuàng)建socket* */struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET; // IPV4serveraddr.sin_port = htons(atoi(argv[2]));// 字符串-> 整型,主機(jī)字節(jié)序->網(wǎng)絡(luò)字節(jié)序inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);if(connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("connect error");exit(1);}// 步驟3:調(diào)用IO函數(shù),和服務(wù)器進(jìn)行雙向通訊char buff[512];size_t size;char * prompt = ">" ;while(1){memset(buff,0,sizeof(buff));write(STDOUT_FILENO,prompt,1);size = read(STDIN_FILENO,buff,sizeof(buff));if(size<0) {continue;}buff[size-1] = '\0';if(write_msg(sockfd,buff,sizeof(buff))<0){perror("write msg error");continue;}else{if(read_msg(sockfd,buff,sizeof(buff))<0){perror("read msg error");continue;}else{printf("%s\n",buff);}}}// 步驟4:關(guān)閉socketclose(sockfd);return 0;
}
- 下面這個(gè)沒有出現(xiàn)
多線程模型
- ehco_tcp_server_th.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include"msg.h"int sockfd;void sig_handler(int signo)
{if(signo==SIGINT){printf("server close\n");/*步驟6:* 關(guān)閉socket* */close(sockfd);exit(1);}
}void do_service(int fd)
{// 和客戶端進(jìn)行讀寫操作(雙向通信)char buff[512];while(1){memset(buff,0,sizeof(buff));printf("start read and write....\n");size_t size;if((size=read_msg(fd,buff,sizeof(buff)))<0){perror("protocal error");break;}else if(size==0){break; // 寫端突然掛了}else{printf("%s\n",buff);if(write_msg(fd,buff,sizeof(buff))<0){if(errno==EPIPE)// 讀端突然關(guān)閉時(shí),類似于管道通信{break;}perror("protocal error");}}}
}void out_fd(int fd)
{struct sockaddr_in addr;socklen_t len = sizeof(addr);// 從fd中獲得連接的客戶端的相關(guān)信息if(getpeername(fd,(struct sockaddr*)&addr,&len)<0){perror("getpeername error");return;}char ip[16];memset(ip,0,sizeof(ip));int port = ntohs(addr.sin_port);inet_ntop(AF_INET,&addr.sin_addr.s_addr,ip,sizeof(ip));printf("%16s(%5d) closed\n",ip,port);
}void * th_fn(void *arg)
{int fd = (int)arg;do_service(fd);out_fd(fd);// 輸出客戶端的信息close(fd);return (void*)0;
}int main(int argc,char *argv[])
{// 參數(shù):服務(wù)器端綁定的端口if(argc<2){printf("usage :%s #port\n",argv[0]);exit(0);}// 綁定一個(gè)信號(hào),ctrl+c 終止服務(wù)端if(signal(SIGINT,sig_handler)==SIG_ERR){perror("signal sigint error");exit(1);}/* * 步驟1. 創(chuàng)建socket,套接字socket創(chuàng)建在內(nèi)核,是一個(gè)結(jié)構(gòu)體* AF_INET : IPV4* SOCK_STREAM:TCP協(xié)議*/sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){perror("sockek error");exit(1);}/** 步驟2:調(diào)用bind函數(shù)將socket和地址(ip和port)進(jìn)行綁定 * */// 專用地址,綁定的時(shí)候在強(qiáng)轉(zhuǎn)為通用地址struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET; // IPV4serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主機(jī)字節(jié)序->網(wǎng)絡(luò)字節(jié)序serveraddr.sin_addr.s_addr = INADDR_ANY;//響應(yīng)所有請(qǐng)求if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("bind error");exit(1);}/**步驟3:調(diào)用listen函數(shù)啟動(dòng)監(jiān)聽(指定port監(jiān)聽)通知系統(tǒng)去接受來自客戶端的連接請(qǐng)求將接受到的客戶端連接請(qǐng)求放置到對(duì)應(yīng)的隊(duì)列中第二個(gè)參數(shù):10:隊(duì)列的長(zhǎng)度* */if(listen(sockfd,10)<0){perror("listen error");exit(1);}/**步驟4:調(diào)用accept函數(shù),從隊(duì)列中獲得一個(gè)客戶端的請(qǐng)求連接* 并返回一個(gè)新的socket描述符*注意:若沒有客戶端連接,調(diào)用此函數(shù)后會(huì)阻塞直到獲得一個(gè)客戶端的連接* */struct sockaddr_in clientaddr;socklen_t clientaddr_len = sizeof(clientaddr);// 設(shè)置線程的分離屬性pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);while(1){// 第二個(gè)參數(shù)可以設(shè)置為NULL,如果不想知道客戶端的信息的話// 主控線程負(fù)責(zé)調(diào)用accept去獲得客戶端的連接 int fd = accept(sockfd,NULL,NULL);if(fd<0){perror("accept error");continue;}/**步驟5:啟動(dòng)子線程調(diào)用IO函數(shù) */pthread_t th;int err;// 以分離狀態(tài)啟動(dòng)子線程if((err=pthread_create(&th,&attr,th_fn,(void*)fd))!=0){perror("pthread create error");}pthread_attr_destroy(&attr);}return 0;
}
I/O多路轉(zhuǎn)換(select)
掌握UDP協(xié)議網(wǎng)絡(luò)基礎(chǔ)編程
發(fā)生數(shù)據(jù)
接收數(shù)據(jù)
- time_udp_server
#include <stdio.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<time.h>
#include<netdb.h>int sockfd;void sig_handler(int signo)
{if(signo==SIGINT){printf("server close\n");close(sockfd);exit(1);}}// 輸出客戶端的信息
void out_addr(struct sockaddr_in *clientaddr)
{char ip[16];memset(ip,0,sizeof(ip));inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));int port = ntohs(clientaddr->sin_port);printf("client : %s(%d)\n",ip,port);
}// 和客戶端進(jìn)行通信
void do_service()
{struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);char buffer[1024];memset(buffer,0,sizeof(buffer));// 接受客戶端的數(shù)據(jù)報(bào)文if(recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&clientaddr,&len)<0){perror("recvfrom error");}else{out_addr(&clientaddr);printf("client send into : %s\n",buffer);//向客戶端發(fā)送數(shù)據(jù)報(bào)文long int t = time(0);char * ptr = ctime(&t);size_t size = strlen(ptr)*sizeof(char);if(sendto(sockfd,ptr,size,0,(struct sockaddr*)&clientaddr,len)<0){perror("sendto error");}}}
int main(int argc,char *argv[])
{if(argc<0){printf("usage : %s port \n",argv[0]);exit(1);}if(signal(SIGINT,sig_handler)==SIG_ERR){perror("signal sigint error");exit(1);}/*步驟1:** */sockfd = socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){perror("sockfd error");exit(1);}int res;int opt = 1;// 設(shè)置套接字選項(xiàng):讓剛才實(shí)現(xiàn)的端口立即啟動(dòng)if((res=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)))<0){perror("setsockopt error");exit(1);}/*步驟2:調(diào)用bind函數(shù)對(duì)socket和地址進(jìn)行綁定* */struct sockaddr_in serveraddr;memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET; // IPV4serveraddr.sin_port = htons(atoi(argv[1])); // portserveraddr.sin_addr.s_addr = INADDR_ANY; //ipif(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("bind error");exit(1);}/*步驟3:和客戶端進(jìn)行雙向的數(shù)據(jù)通信* */while(1){do_service();}return 0;
}
- time_udp_client
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netdb.h>
#include<sys/socket.h>
#include<memory.h>
#include<unistd.h>int main(int argc,char *argv[])
{if(argc<3){printf("usgae : %s ip port \n",argv[0]);exit(1);}/*步驟1:創(chuàng)建socket* */int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){perror("socket error");exit(1);}/*步驟2:調(diào)用recvfrom和sendto等函數(shù)* 和服務(wù)器進(jìn)行雙向通信* */struct sockaddr_in serveraddr;memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr); //ipchar buffer[1024] = "hello iotek";// 向服務(wù)器端發(fā)送數(shù)據(jù)報(bào)文if(sendto(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("sendto error");exit(1);}else{//接受服務(wù)器發(fā)送的報(bào)文memset(buffer,0,sizeof(buffer));if(recv(sockfd,buffer,sizeof(buffer),0)<0){perror("recv error");exit(1);}else{printf("%s",buffer);}close(sockfd);}return 0;
}
- 在客戶端可以使用connect進(jìn)行連接
這里的connect并沒有和服務(wù)端進(jìn)行三次握手,只是在內(nèi)核中記錄了服務(wù)端的地址信息和端口,所以可以直接使用send不用指出服務(wù)端的地址等信息了。而且調(diào)用connect可以保證只是接受來自服務(wù)端發(fā)送的數(shù)據(jù),不接受其他的信息
- 多次綁定同一個(gè)端口
第二個(gè)端口起作用
域名
- gethostent獲取所有
- gethostbuname獲取某一個(gè)
-
在Linux中的,位于
/etc/hosts
-
gethost.c
#include <stdio.h>
#include<netdb.h>
#include<stdlib.h>
#include<memory.h>void out_addr(struct hostent *h)
{printf("hostbyname: %s\n",h->h_name);printf("addrtype: %s\n",h->h_addrtype==AF_INET ? "IPV4" : "IPV6");char ip[16];memset(ip,0,sizeof(ip));inet_ntop(h->h_addrtype,h->h_addr_list[0],ip,sizeof(ip));printf("ip addrress : %s\n",ip);int i = 0;while(h->h_aliases[i] != NULL){printf("alias : %s \n",h->h_aliases[i]);i++;}
}int main(int argc,char *argv[])
{if(argc<2){printf("usage %s host \n",argv[0]);exit(1);}struct hostent *h;h = gethostbyname(argv[1]);if(h!=NULL){out_addr(h);}else{printf("no %s exits\n",argv[1]);}return 0;endhostent();
}
注意gethostbyname應(yīng)該避免在多線程下使用。
- 使用gethostent
#include <stdio.h>
#include<netdb.h>
#include<stdlib.h>
#include<memory.h>void out_addr(struct hostent *h)
{printf("hostbyname: %s\n",h->h_name);printf("addrtype: %s\n",h->h_addrtype==AF_INET ? "IPV4" : "IPV6");char ip[16];memset(ip,0,sizeof(ip));inet_ntop(h->h_addrtype,h->h_addr_list[0],ip,sizeof(ip));printf("ip addrress : %s\n",ip);int i = 0;while(h->h_aliases[i] != NULL){printf("alias : %s \n",h->h_aliases[i]);i++;}
}int main(int argc,char *argv[])
{if(argc<2){printf("usage %s host \n",argv[0]);exit(1);}struct hostent *h;while((h=gethostent()) != NULL){if(!strcmp(argv[1],h->h_name)){out_addr(h);exit(0);}else{int i = 0;while(h->h_aliases[i] != NULL){if(!strcmp(argv[1],h->h_aliases[i])){out_addr(h);exit(0);}i++;}}}endhostent();return 0;
}
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netdb.h>
#include<sys/socket.h>
#include<memory.h>
#include<unistd.h>int is_host(struct hostent *host,char *name)
{if(!strcmp(host->h_name,name))return 1;int i =0;while(host->h_aliases[i] != NULL){if(!strcmp(host->h_aliases[i],name))return 1;i++;}
}
unsigned int get_ip_by_name(char *name)
{unsigned int ip = 0;struct hostent *host;while((host = gethostent())!=NULL){if(is_host(host,name)){memcpy(&ip,host->h_addr_list[0],4);break;}}endhostent();return ip;
}int main(int argc,char *argv[])
{if(argc<3){printf("usgae : %s ip port \n",argv[0]);exit(1);}/*步驟1:創(chuàng)建socket* */int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){perror("socket error");exit(1);}/*步驟2:調(diào)用recvfrom和sendto等函數(shù)* 和服務(wù)器進(jìn)行雙向通信* */struct sockaddr_in serveraddr;memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));unsigned int ip = get_ip_by_name(argv[1]);if(ip!=0){serveraddr.sin_addr.s_addr = ip;}else{inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);}//inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr); //ipchar buffer[1024] = "hello iotek";// 向服務(wù)器端發(fā)送數(shù)據(jù)報(bào)文if(sendto(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("sendto error");exit(1);}else{//接受服務(wù)器發(fā)送的報(bào)文memset(buffer,0,sizeof(buffer));if(recv(sockfd,buffer,sizeof(buffer),0)<0){perror("recv error");exit(1);}else{printf("%s",buffer);}close(sockfd);}return 0;
}
網(wǎng)絡(luò)高級(jí)編程
廣播
套接字選項(xiàng)
TCP不支持廣播,只有UDP才能支持廣播
緩存區(qū)
- receiver.c
#include <stdio.h>
#include<netdb.h>
#include<sys/socket.h>
#include<string.h>
#include<signal.h>
#include<stdlib.h>int sockfd;void sig_handler(int signo)
{if(signo == SIGINT){printf("receiver will exited");close(sockfd);exit(1);}
}int main(int argc,char *argv[])
{if(argc<2){fprintf(stderr,"usage : %s port \n",argv[0]);exit(1);}if(signal(SIGINT,sig_handler)==SIG_ERR){perror("signal sigint errro");exit(1);}sockfd = socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){perror("socket error");exit(1);}struct sockaddr_in serveraddr;memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[1]));serveraddr.sin_addr.s_addr = INADDR_ANY;if(bind(sockfd,(struct sockadd*)&serveraddr,sizeof(serveraddr))<0){perror("bind error");exit(1);}char buffer[1024];struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);while(1){memset(buffer,0,sizeof(buffer));memset(&clientaddr,0,sizeof(clientaddr));if(recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&clientaddr,&len)<0){perror("recvfrom error");exit(1);}else{char ip[16];inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ip,sizeof(ip));int port = ntohs(clientaddr.sin_port); printf("%s{%d}:%s\n",ip,port,buffer);}}return 0;
}
- broadcast.c
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netdb.h>
#include<sys/socket.h>int main(int argc,char * argv[])
{if(argc<3){fprintf(stderr,"usage: %s ip port\n",argv[0]);exit(1);}int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){perror("sockdt error");exit(1);}int opt = 1;// 采用廣播地址發(fā)送setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt));struct sockaddr_in serveraddr;memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);printf("I will broadcast....\n");char *info = "helo yangpipi...";size_t size = strlen(info)*sizeof(char);if(sendto(sockfd,info,size,0,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("sendto error");exit(1);}else{printf("boradcast success\n");}close(sockfd);return 0;
多路復(fù)用之fcntl
- server.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include<fcntl.h>
#include"vector_fd.h"VectorFD *vfd;
int sockfd;void sig_handler(int signo)
{if(signo==SIGINT){printf("server close----\n");/*步驟6:* 關(guān)閉socket* */close(sockfd);// 銷毀動(dòng)態(tài)數(shù)組destroy_vector_fd(vfd);exit(1);}
}/**fd對(duì)應(yīng)于某個(gè)連接的客戶端,和某個(gè)連接的客戶端進(jìn)行雙向通信(非阻塞方式)* */
void do_service(int fd)
{// 和客戶端進(jìn)行讀寫操作(雙向通信)char buff[512];while(1){memset(buff,0,sizeof(buff));// 以非阻塞方式讀,讀不到數(shù)據(jù)就返回了,直接服務(wù)于下一個(gè)客戶端,因此不需要判斷size<0的情況size_t size=read(fd,buff,sizeof(buff));if(size==0){char info[] = "client closed";write(STDOUT_FILENO,info,sizeof(info));// 從動(dòng)態(tài)數(shù)組中刪除對(duì)應(yīng)的fdremove_fd(vfd,fd);// 關(guān)閉對(duì)應(yīng)的客戶端的socketclose(fd);}else if(size > 0){write(STDOUT_FILENO,buff,sizeof(buff));if(write(fd,buff,size)<size){if(errno==EPIPE)// 客戶端關(guān)閉連接{perror("write error");remove_fd(vfd,fd);close(fd);}}}}
}void * th_fn(void *arg)
{int i;while(1){// 遍歷動(dòng)態(tài)數(shù)中的socket描述符for(int i = 0;i<vfd->counter;i++){do_service(get_fd(vfd,i));}}return (void*)0;
}void out_addr(struct sockaddr_in *clientaddr)
{char ip[16];memset(ip,0,sizeof(ip));int port = ntohs(clientaddr->sin_port);inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));printf("%s(%d) connected!\n",ip,port);
}
int main(int argc,char *argv[])
{// 參數(shù):服務(wù)器端綁定的端口if(argc<2){printf("usage :%s #port\n",argv[0]);exit(0);}// 綁定一個(gè)信號(hào),ctrl+c 終止服務(wù)端if(signal(SIGINT,sig_handler)==SIG_ERR){perror("signal sigint error");exit(1);}/* * 步驟1. 創(chuàng)建socket,套接字socket創(chuàng)建在內(nèi)核,是一個(gè)結(jié)構(gòu)體* AF_INET : IPV4* SOCK_STREAM:TCP協(xié)議*/sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){perror("sockek error");exit(1);}/** 步驟2:調(diào)用bind函數(shù)將socket和地址(ip和port)進(jìn)行綁定 * */// 專用地址,綁定的時(shí)候在強(qiáng)轉(zhuǎn)為通用地址struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET; // IPV4serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主機(jī)字節(jié)序->網(wǎng)絡(luò)字節(jié)序serveraddr.sin_addr.s_addr = INADDR_ANY;//響應(yīng)所有請(qǐng)求if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("bind error");exit(1);}/**步驟3:調(diào)用listen函數(shù)啟動(dòng)監(jiān)聽(指定port監(jiān)聽)通知系統(tǒng)去接受來自客戶端的連接請(qǐng)求將接受到的客戶端連接請(qǐng)求放置到對(duì)應(yīng)的隊(duì)列中第二個(gè)參數(shù):10:隊(duì)列的長(zhǎng)度* */if(listen(sockfd,10)<0){perror("listen error");exit(1);}/**步驟4:調(diào)用accept函數(shù),從隊(duì)列中獲得一個(gè)客戶端的請(qǐng)求連接* 并返回一個(gè)新的socket描述符*注意:若沒有客戶端連接,調(diào)用此函數(shù)后會(huì)阻塞直到獲得一個(gè)客戶端的連接* */// 創(chuàng)建放置套接字描述符fd的動(dòng)態(tài)數(shù)組vfd = create_vector_fd();// 設(shè)置線程的分離屬性pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);int err;pthread_t th; if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0){perror("pthread create error");exit(1);}pthread_attr_destroy(&attr);/** 1) 主控線程獲得客戶端的連接,將新的socket描述符放置到動(dòng)態(tài)數(shù)組中* 2) 啟動(dòng)的子線程負(fù)責(zé)遍歷動(dòng)態(tài)數(shù)組中socket描述符并和對(duì)應(yīng)的客戶端進(jìn)行雙向通信(采用非阻塞方式讀寫)** */struct sockaddr_in clientaddr;socklen_t clientaddr_len = sizeof(clientaddr);while(1){int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);if(fd<0){perror("accept error");continue;}out_addr(&clientaddr);// 將讀寫方式修改為非阻塞方式int val;fcntl(fd,F_GETFL,&val);val |= O_NONBLOCK;fcntl(fd,F_SETFL,val); // 將返回新的socket描述符加入到動(dòng)態(tài)數(shù)組中add_fd(vfd,fd);}return 0;
- client.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>int main(int argc,char *argv[])
{// 參數(shù):服務(wù)器端綁定的端口if(argc<2){printf("usage :%s #port\n",argv[0]);exit(0);}/* * 步驟1. 創(chuàng)建socket,套接字socket創(chuàng)建在內(nèi)核,是一個(gè)結(jié)構(gòu)體* AF_INET : IPV4* SOCK_STREAM:TCP協(xié)議*/int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){perror("sockek error");exit(1);}/** 步驟2:創(chuàng)建socket* */struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET; // IPV4serveraddr.sin_port = htons(atoi(argv[2]));// 字符串-> 整型,主機(jī)字節(jié)序->網(wǎng)絡(luò)字節(jié)序inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);if(connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("connect error");exit(1);}// 步驟3:調(diào)用IO函數(shù),和服務(wù)器進(jìn)行雙向通訊char buff[512];size_t size;char * prompt = ">" ;while(1){memset(buff,0,sizeof(buff));write(STDOUT_FILENO,prompt,1);size = read(STDIN_FILENO,buff,sizeof(buff));if(size<0) {continue;}buff[size-1] = '\0';if(write(sockfd,buff,sizeof(buff))<0){perror("write msg error");continue;}else{if(read(sockfd,buff,sizeof(buff))<0){perror("read msg error");continue;}else{printf("%s\n",buff);}}}// 步驟4:關(guān)閉socketclose(sockfd);return 0;
}
多路復(fù)用之select
- echo_tcp_sever_select.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include<fcntl.h>
#include"vector_fd.h"VectorFD *vfd;
int sockfd;void sig_handler(int signo)
{if(signo==SIGINT){printf("server close----\n");/*步驟6:* 關(guān)閉socket* */close(sockfd);// 銷毀動(dòng)態(tài)數(shù)組destroy_vector_fd(vfd);exit(1);}
}/**fd對(duì)應(yīng)于某個(gè)連接的客戶端,和某個(gè)連接的客戶端進(jìn)行雙向通信(非阻塞方式)* */
void do_service(int fd)
{// 和客戶端進(jìn)行讀寫操作(雙向通信)char buff[512];while(1){memset(buff,0,sizeof(buff));// 以非阻塞方式讀,讀不到數(shù)據(jù)就返回了,直接服務(wù)于下一個(gè)客戶端,因此不需要判斷size<0的情況size_t size=read(fd,buff,sizeof(buff));if(size==0){//char info[] = "client closed";//write(STDOUT_FILENO,info,sizeof(info));printf("client closed\n"); // 這里由于內(nèi)核設(shè)置,所以可以使用帶緩存的IO// 從動(dòng)態(tài)數(shù)組中刪除對(duì)應(yīng)的fdremove_fd(vfd,fd);// 關(guān)閉對(duì)應(yīng)的客戶端的socketclose(fd);}else if(size > 0){//write(STDOUT_FILENO,buff,sizeof(buff));printf("%s\n",buff);if(write(fd,buff,size)<size){if(errno==EPIPE)// 客戶端關(guān)閉連接{perror("write error");remove_fd(vfd,fd);close(fd);}}}}
}/*遍歷出動(dòng)態(tài)數(shù)組中所有的描述符并加入到描述符集set中* 同時(shí)此函數(shù)返回動(dòng)態(tài)數(shù)組中最大的那個(gè)描述符* */
int add_set(fd_set *set)
{FD_ZERO(set); // 清空描述符int max_fd = vfd->fd[0];for(int i = 0;i < vfd->fd[0];i++){int fd = get_fd(vfd,i);if(fd>max_fd){max_fd = fd;}FD_SET(fd,set);//將fd加入到描述符集中}return max_fd;
}
void * th_fn(void *arg)
{struct timeval t;t.tv_sec = 2;t.tv_usec = 0;int n = 0;int maxfd;fd_set set; // 描述符集maxfd = add_set(&set);/*調(diào)用select函數(shù)會(huì)阻塞,委托內(nèi)核去檢查傳入的描述符是否準(zhǔn)備好,* 如有則返回準(zhǔn)備好的描述符,* 超時(shí)則返回0* 第一個(gè)參數(shù)為描述符集中描述符的范圍(最大描述符+1)* */while((n=select(maxfd+1,&set,NULL,NULL,&t))>=0){if(n>0){/*檢測(cè)那些描述符準(zhǔn)備好,并和這些準(zhǔn)備好的描述符對(duì)應(yīng)的客戶端進(jìn)行數(shù)據(jù)的雙向通信* */for(int i=0;i<vfd->counter;i++){int fd = get_fd(vfd,i);if(FD_ISSET(fd,&set)){do_service(fd);}}}//超時(shí)// 重新設(shè)置時(shí)間和清空描述符集t.tv_sec = 2;t.tv_usec = 0;// 重新遍歷動(dòng)態(tài)數(shù)組中最新的描述符放置到描述符集中maxfd = add_set(&set);}
}void out_addr(struct sockaddr_in *clientaddr)
{char ip[16];memset(ip,0,sizeof(ip));int port = ntohs(clientaddr->sin_port);inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));printf("%s(%d) connected!\n",ip,port);
}
int main(int argc,char *argv[])
{// 參數(shù):服務(wù)器端綁定的端口if(argc<2){printf("usage :%s #port\n",argv[0]);exit(0);}// 綁定一個(gè)信號(hào),ctrl+c 終止服務(wù)端if(signal(SIGINT,sig_handler)==SIG_ERR){perror("signal sigint error");exit(1);}/* * 步驟1. 創(chuàng)建socket,套接字socket創(chuàng)建在內(nèi)核,是一個(gè)結(jié)構(gòu)體* AF_INET : IPV4* SOCK_STREAM:TCP協(xié)議*/sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){perror("sockek error");exit(1);}/** 步驟2:調(diào)用bind函數(shù)將socket和地址(ip和port)進(jìn)行綁定 * */// 專用地址,綁定的時(shí)候在強(qiáng)轉(zhuǎn)為通用地址struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET; // IPV4serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主機(jī)字節(jié)序->網(wǎng)絡(luò)字節(jié)序serveraddr.sin_addr.s_addr = INADDR_ANY;//響應(yīng)所有請(qǐng)求if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){perror("bind error");exit(1);}/**步驟3:調(diào)用listen函數(shù)啟動(dòng)監(jiān)聽(指定port監(jiān)聽)通知系統(tǒng)去接受來自客戶端的連接請(qǐng)求將接受到的客戶端連接請(qǐng)求放置到對(duì)應(yīng)的隊(duì)列中第二個(gè)參數(shù):10:隊(duì)列的長(zhǎng)度* */if(listen(sockfd,10)<0){perror("listen error");exit(1);}/**步驟4:調(diào)用accept函數(shù),從隊(duì)列中獲得一個(gè)客戶端的請(qǐng)求連接* 并返回一個(gè)新的socket描述符*注意:若沒有客戶端連接,調(diào)用此函數(shù)后會(huì)阻塞直到獲得一個(gè)客戶端的連接* */// 創(chuàng)建放置套接字描述符fd的動(dòng)態(tài)數(shù)組vfd = create_vector_fd();// 設(shè)置線程的分離屬性pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);int err;pthread_t th; if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0){perror("pthread create error");exit(1);}pthread_attr_destroy(&attr);/** 1) 主控線程獲得客戶端的連接,將新的socket描述符放置到動(dòng)態(tài)數(shù)組中* 2) * a) 啟動(dòng)的子線程調(diào)用select函數(shù)委托內(nèi)核去檢查傳入到select中的描述符是否準(zhǔn)備好* b) 利用FD_ISSET來找出準(zhǔn)備好的那些描述符并和對(duì)應(yīng)的客戶端進(jìn)行雙向通信(非阻塞)** */struct sockaddr_in clientaddr;socklen_t clientaddr_len = sizeof(clientaddr);while(1){int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);if(fd<0){perror("accept error");continue;}out_addr(&clientaddr);// 將返回新的socket描述符加入到動(dòng)態(tài)數(shù)組中add_fd(vfd,fd);}return 0;
}
守護(hù)進(jìn)程
- 編程步驟
- 出錯(cuò)處理
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include<fcntl.h>
#include<syslog.h>
#include<sys/stat.h>
#include"vector_fd.h"VectorFD *vfd;
int sockfd;/**fd對(duì)應(yīng)于某個(gè)連接的客戶端,和某個(gè)連接的客戶端進(jìn)行雙向通信(非阻塞方式)* */
void do_service(int fd)
{// 和客戶端進(jìn)行讀寫操作(雙向通信)char buff[512];while(1){memset(buff,0,sizeof(buff));// 以非阻塞方式讀,讀不到數(shù)據(jù)就返回了,直接服務(wù)于下一個(gè)客戶端,因此不需要判斷size<0的情況size_t size=read(fd,buff,sizeof(buff));if(size==0){char info[] = "client closed\n";syslog(LOG_DEBUG,"client closed");//printf("client closed\n"); // 這里由于內(nèi)核設(shè)置,所以可以使用帶緩存的IO// 從動(dòng)態(tài)數(shù)組中刪除對(duì)應(yīng)的fdremove_fd(vfd,fd);// 關(guān)閉對(duì)應(yīng)的客戶端的socketclose(fd);}else if(size > 0){//write(STDOUT_FILENO,buff,sizeof(buff));// printf("%s\n",buff);syslog(LOG_DEBUG,"%s\n",buff);if(write(fd,buff,size)<size){if(errno==EPIPE)// 客戶端關(guān)閉連接{syslog(LOG_DEBUG,"write:%s\n",strerror(errno));remove_fd(vfd,fd);close(fd);}}}}
}/*遍歷出動(dòng)態(tài)數(shù)組中所有的描述符并加入到描述符集set中* 同時(shí)此函數(shù)返回動(dòng)態(tài)數(shù)組中最大的那個(gè)描述符* */
int add_set(fd_set *set)
{FD_ZERO(set); // 清空描述符int max_fd = vfd->fd[0];for(int i = 0;i < vfd->fd[0];i++){int fd = get_fd(vfd,i);if(fd>max_fd){max_fd = fd;}FD_SET(fd,set);//將fd加入到描述符集中}return max_fd;
}
void * th_fn(void *arg)
{struct timeval t;t.tv_sec = 2;t.tv_usec = 0;int n = 0;int maxfd;fd_set set; // 描述符集maxfd = add_set(&set);/*調(diào)用select函數(shù)會(huì)阻塞,委托內(nèi)核去檢查傳入的描述符是否準(zhǔn)備好,* 如有則返回準(zhǔn)備好的描述符,* 超時(shí)則返回0* 第一個(gè)參數(shù)為描述符集中描述符的范圍(最大描述符+1)* */while((n=select(maxfd+1,&set,NULL,NULL,&t))>=0){if(n>0){/*檢測(cè)那些描述符準(zhǔn)備好,并和這些準(zhǔn)備好的描述符對(duì)應(yīng)的客戶端進(jìn)行數(shù)據(jù)的雙向通信* */for(int i=0;i<vfd->counter;i++){int fd = get_fd(vfd,i);if(FD_ISSET(fd,&set)){do_service(fd);}}}//超時(shí)// 重新設(shè)置時(shí)間和清空描述符集t.tv_sec = 2;t.tv_usec = 0;// 重新遍歷動(dòng)態(tài)數(shù)組中最新的描述符放置到描述符集中maxfd = add_set(&set);}
}void out_addr(struct sockaddr_in *clientaddr)
{char ip[16];memset(ip,0,sizeof(ip));int port = ntohs(clientaddr->sin_port);inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));syslog(LOG_DEBUG,"%s(%d) connected!\n",ip,port);
}
int main(int argc,char *argv[])
{// 參數(shù):服務(wù)器端綁定的端口if(argc<2){printf("usage :%s #port\n",argv[0]);exit(1);}// 守護(hù)進(jìn)程編程的5個(gè)步驟// 步驟1:創(chuàng)建屏蔽字為0umask(0);// 步驟2:調(diào)用fork函數(shù)創(chuàng)建子進(jìn)程,然后父進(jìn)程退出pid_t pid = fork();if(pid>0) exit(0);//步驟3:調(diào)用setsid函數(shù)創(chuàng)建一個(gè)新的會(huì)話setsid();//步驟4:將當(dāng)前工作目錄更改為根目錄chdir("/");//步驟5:關(guān)閉不需要的文件描述符close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);//打開系統(tǒng)日志服務(wù)的一個(gè)連接openlog(argv[0],LOG_PID,LOG_SYSLOG);/* * 步驟1. 創(chuàng)建socket,套接字socket創(chuàng)建在內(nèi)核,是一個(gè)結(jié)構(gòu)體* AF_INET : IPV4* SOCK_STREAM:TCP協(xié)議*/sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){syslog(LOG_DEBUG,"socket:%s\n",strerror(errno));exit(1);}/** 步驟2:調(diào)用bind函數(shù)將socket和地址(ip和port)進(jìn)行綁定 * */// 專用地址,綁定的時(shí)候在強(qiáng)轉(zhuǎn)為通用地址struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET; // IPV4serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主機(jī)字節(jié)序->網(wǎng)絡(luò)字節(jié)序serveraddr.sin_addr.s_addr = INADDR_ANY;//響應(yīng)所有請(qǐng)求if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0){// 將日志信息寫入到系統(tǒng)日志文件中(/var/log/syslog)syslog(LOG_DEBUG,"bind:%s\n",strerror(errno));exit(1);}/**步驟3:調(diào)用listen函數(shù)啟動(dòng)監(jiān)聽(指定port監(jiān)聽)通知系統(tǒng)去接受來自客戶端的連接請(qǐng)求將接受到的客戶端連接請(qǐng)求放置到對(duì)應(yīng)的隊(duì)列中第二個(gè)參數(shù):10:隊(duì)列的長(zhǎng)度* */if(listen(sockfd,10)<0){syslog(LOG_DEBUG,"listen:%s\n",strerror(errno));exit(1);}/**步驟4:調(diào)用accept函數(shù),從隊(duì)列中獲得一個(gè)客戶端的請(qǐng)求連接* 并返回一個(gè)新的socket描述符*注意:若沒有客戶端連接,調(diào)用此函數(shù)后會(huì)阻塞直到獲得一個(gè)客戶端的連接* */// 創(chuàng)建放置套接字描述符fd的動(dòng)態(tài)數(shù)組vfd = create_vector_fd();// 設(shè)置線程的分離屬性pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);int err;pthread_t th; if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0){syslog(LOG_DEBUG,"pthread:%s\n",strerror(errno));exit(1);}pthread_attr_destroy(&attr);/** 1) 主控線程獲得客戶端的連接,將新的socket描述符放置到動(dòng)態(tài)數(shù)組中* 2) * a) 啟動(dòng)的子線程調(diào)用select函數(shù)委托內(nèi)核去檢查傳入到select中的描述符是否準(zhǔn)備好* b) 利用FD_ISSET來找出準(zhǔn)備好的那些描述符并和對(duì)應(yīng)的客戶端進(jìn)行雙向通信(非阻塞)** */struct sockaddr_in clientaddr;socklen_t clientaddr_len = sizeof(clientaddr);while(1){int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);if(fd<0){syslog(LOG_DEBUG,"accept:%s\n",strerror(errno));continue;}out_addr(&clientaddr);// 將返回新的socket描述符加入到動(dòng)態(tài)數(shù)組中add_fd(vfd,fd);}return 0;
}