經過一段時間的學習、積累,自己對於物聯網、計算機網絡、socket編程等相關的領域和知識有了新的理解,網絡部分一直是重中之重。因此重新再學習了Linux下的socket,並結合了一個簡單的實際例子再來學習client端與server端工作流程。
===========================================
文件描述符:實際上就是Linux內核給分配的“稱謂”,而在我們的TCP通訊中,會有多個文件描述符需要處理。
比如:
listenfd:監聽描述符
connectfd:請求連接描述符
accept:接受連接描述符
read/write/recv/send…:IO描述符
服務器建立連接的流程和涉及到的函數:socket()、bind()、listen()、accept()、connect()、close()。
結構體struct sockaddr_in :網絡通訊五元組,本端IP,本端端口、對端IP、對端端口、協議類型。
參考這張圖便能瞭解client和server之間之間是如何進行交互的了。
===========================================
int socket(int domain, int type, int protocol);
向內核申請一個套接字,設置該套接字協議類型。
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
爲套接字綁定IP和端口
int listen(int sockfd, int backlog);
以socket套接字和該套接字綁定的IP信息listen在內核開啓監聽,並返回監聽描述符。這裏代碼是不阻塞的,但是內核一直在監聽對應的端口。
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
連接請求,此時代碼是阻塞的。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
接受連接的請求,代碼阻塞。就是從listen中取描述符。
int close(int fd);
關閉描述符。
是不是就是類似我們熟悉的三次握手呀。
Client請求時間是不確定的,當多個請求到Server時,處於請求隊列,等待listen的端口逐個處理至就緒隊列。
connect處於阻塞態等待請求從listen的就緒隊列被accept調度返回具體用於數據傳輸的accept_fd描述符。
accept處於阻塞態,當請求隊列爲空或處理完畢時。
所以,三次握手由connet發起,accept結束,途中經歷listen的隊列維護。
下面貼出代碼,功能是由在開發板上的DS18B20採集了溫度數據,上報到服務器,並且顯示當前的時間。當然,前提是開發板已經使能了DS18B20的驅動,否則是跑不起應用程序的。
DS18B20的驅動編寫可以參考這裏:http://blog.csdn.net/u010944778/article/details/48058433
temp_server.c:
/*********************************************************************************
* Copyright: (C) 2017 TangBin<[email protected]>
* All rights reserved.
*
* Filename: temp_server.c
* Description: This file
*
* Version: 1.0.0(06/18/2017)
* Author: TangBin <[email protected]>
* ChangeLog: 1, Release initial version on "06/18/2017 08:22:56 PM"
*
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <time.h>
#define MAXLINE 1024
#define PORT 8200
#define BUFFSIZE 2048
int main (int argc, char **argv)
{
int listenfd, connfd;
int n;
char buf[BUFFSIZE];
struct sockaddr_in servaddr;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("create socket error:");
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
perror("bind error:");
exit(0);
}
if(listen(listenfd, 10) < 0)
{
perror("listen error:");
exit(0);
}
printf("waiting client's request... ...\n");
while(1)
{
memset(&buf, 0, sizeof(buf));
time_t rawtime;
struct tm* ptime;
char time_arr[100];
time(&rawtime);
ptime = localtime(&rawtime);
strftime(time_arr, 20, "%y-%m-%d %I:%M:%S", ptime);
if((connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) < 0)
{
perror("accept error:");
continue;
}
n = recv(connfd, buf, MAXLINE, 0);
strcat(buf, "℃\0");
printf("temperature :%s\n", buf);
printf("%s\n",time_arr);
}
close(connfd);
return 0;
} /* ----- End of main() ----- */
temp_client.c:
/*********************************************************************************
* Copyright: (C) 2017 TangBin<[email protected]>
* All rights reserved.
*
* Filename: temp_client.c
* Description: This file
*
* Version: 1.0.0(06/19/2017)
* Author: TangBin <[email protected]>
* ChangeLog: 1, Release initial version on "06/19/2017 12:52:25 PM"
*
********************************************************************************/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 1024
#define PORT 8200
float get_temp(void)
{
int fd;
int data = 0;
float temperature = 0;
unsigned char buff[2];
if((fd=open("/dev/ds18b20",O_RDWR | O_NDELAY | O_NOCTTY)) < 0)
{
perror("open device ds18b20 failed.");
exit(1);
}
printf("open ds18b20 success.");
read(fd, buff, sizeof(buff));
data = ((int)buff[1]) << 8; //高8位移動到16BITS的高8位
data |= (int)buff[0]; //合併低8位到溫度讀值
temperature = ((float)data)*0.0625;
/*DS18B20的溫度操作是使用16位,也就是說分辨率是0.0625;要求出正數的十進制值,必須將讀取到的LSB字節,MSB字節進行整合處理,然後乘以0.0625即可*/
close(fd);
printf("emperature is %4f \r\n",temperature);
return temperature;
}
int main (int argc, char **argv)
{
float res;
int socketfd;
char temp[50];
char s_line[MAXLINE];
struct sockaddr_in servaddr;
if(2 != argc)
{
printf("please input: ./client <ipaddr>\n");
return 0;
}
if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("create socket error: %s(errno: %d).\n", strerror(errno), errno);
return 0;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) //點分十進制ip轉換爲點分二進制ip
{
printf("inet_pton error for %s.\n", argv[1]);
return 0;
}
if(connect(socketfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
printf("connect error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
res = get_temp();
gcvt(res ,4 ,temp); //浮點型數轉換爲字符串,參數1:被轉換的值;參數2:存儲的有效數字位數;參數3:結果的存儲位置。
memset(s_line ,0, sizeof(s_line));
strcpy(s_line, temp);
if(send(socketfd, s_line, strlen(s_line), 0) < 0) //將數據發送到服務器端
{
printf("send error:%s(errno: %d)\n", strerror(errno), errno);
return 0;
}
close(socketfd);
return 0;
} /* ----- End of main() ----- */
測試結果,用的是實驗室的服務器和自己的開發板fl2440:
(其中connect error是因爲在服務器上我的server程序沒運行)