server.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#define BUFLEN 10
int main(int argc, char **argv)
{
int sockfd;
int newfd;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
char buf[BUFLEN];
socklen_t len;
/*任何完整的庫必須定義socklen_t和int相同的尺寸大小*/
unsigned int listnum;
unsigned int port;
/*建立socket*/
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
/* perror(s) 用來將上一個函數發生錯誤的原因輸出到標准設備(stderr)。參數 s 所指的字符串會先打印出,後面再加上錯誤原因字符串。此錯誤原因依照全局變量errno(這裡的說法不准確,errno是一個宏,該宏返回左值) 的值來決定要輸出的字符串。
在庫函數中有個errno變量,每個errno值對應著以字符串表示的錯誤類型。當你調用"某些"函數出錯時,該函數已經重新設置了errno的值。perror函數只是將你輸入的一些信息和現在的errno所對應的錯誤一起輸出。*/
exit(errno);
}
else
printf("socket create success!\n");
/*設置服務器端口*/
if(argv[2])
port = atoi(argv[2]);
else
port = 4567;
/*設置偵聽隊列長度*/
if(argv[3])
listnum = atoi(argv[3]);
else
listnum = 3;
/*設置服務器ip*/
bzero(&s_addr, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(port);
if(argv[1])
s_addr.sin_addr.s_addr = inet_addr(argv[1]);
else
s_addr.sin_addr.s_addr = INADDR_ANY;
/*把地址和端口幫定到套接字上*/
if((bind(sockfd, (struct sockaddr*) &s_addr, sizeof(struct sockaddr))) == -1)
{
perror("bind");
exit(errno);
}
else
printf("bind success!\n");
/*偵聽本地端口*/
if(listen(sockfd, listnum) == -1)
{
perror("listen");
exit(errno);
}
else
printf("the server is listening!\n");
while(1)
{
printf("*****************聊天開始***************\n");
len = sizeof(struct sockaddr);
if((newfd = accept(sockfd, (struct sockaddr*)&c_addr, &len)) == -1)
{
perror("accept");
exit(errno);
}
else
printf("正在與您聊天的客戶端是:%s: %d\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
while(1)
{
_retry:
/******發送消息*******/
bzero(buf, BUFLEN);
printf("請輸入發送給對方的消息:");
/*fgets函數:從流中讀取BUFLEN-1個字符*/
fgets(buf, BUFLEN, stdin);
/*打印發送的消息*/
//fputs(buf,stdout);
if(!strncasecmp(buf, "quit", 4))
/* 函數說明:strncasecmp()用來比較參數s1和s2字符串前n個字符,比較時會自動忽略大小寫的差異.若參數s1和s2字符串相同,則返回0; 若s1大於s2,則返回大於0的值;若s1小於s2,則返回小於0的值。*/
{
printf("server 請求終止聊天!\n");
break;
}
/*如果輸入的字符串只有"\n",即回車,那麼請重新輸入*/
if(!strncmp(buf, "\n", 1))
{
printf("輸入的字符只有回車,這個是不正確的!!!\n");
goto _retry;
}
/*如果buf中含有'\n',那麼要用strlen(buf)-1,去掉'\n'*/
if(strchr(buf, '\n'))
/* extern char *strchr(const char *s,char c);功能:查找字符串s中首次出現字符c的位置.說明:返回首次出現c的位置的指針,返回的地址是被查找字符串指針開始的第一個與Val相同字符的指針,如果s中不存在c則返回NULL。
返回值:成功則返回要查找字符第一次出現的位置,失敗返回NULL */
len = send(newfd, buf, strlen(buf)-1, 0);
/*1 #include <sys/socket.h>
2 ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
3 ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
flags參數值為0或:
flags 說明 recv send
MSG_DONTROUTE 繞過路由表查找 •
MSG_DONTWAIT 僅本操作非阻塞 • •
MSG_OOB 發送或接收帶外數據 • •
MSG_PEEK 窺看外來消息 •
MSG_WAITALL 等待所有數據 •
1. send解析
sockfd:指定發送端套接字描述符。
buff: 存放要發送數據的緩沖區
nbytes: 實際要改善的數據的字節數
flags: 一般設置為0
1) send先比較發送數據的長度nbytes和套接字sockfd的發送緩沖區的長度,如果nbytes > 套接字sockfd的發送緩沖區的長度, 該函數返回SOCKET_ERROR;
2) 如果nbtyes <= 套接字sockfd的發送緩沖區的長度,那麼send先檢查協議是否正在發送sockfd的發送緩沖區中的數據,如果是就等待協議把數據發送完,如果協議還沒有開始發送sockfd的發送緩沖區中的數據或者sockfd的發送緩沖區中沒有數據,那麼send就比較sockfd的發送緩沖區的剩余空間和nbytes
3) 如果 nbytes > 套接字sockfd的發送緩沖區剩余空間的長度,send就一起等待協議把套接字sockfd的發送緩沖區中的數據發送完
4) 如果 nbytes < 套接字sockfd的發送緩沖區剩余空間大小,send就僅僅把buf中的數據copy到剩余空間裡(注意並不是send把套接字sockfd的發送緩沖區中的數據傳到連接的另一端的,而是協議傳送的,send僅僅是把buf中的數據copy到套接字sockfd的發送緩沖區的剩余空間裡)。
5) 如果send函數copy成功,就返回實際copy的字節數,如果send在copy數據時出現錯誤,那麼send就返回SOCKET_ERROR; 如果在等待協議傳送數據時網絡斷開,send函數也返回SOCKET_ERROR。
6) send函數把buff中的數據成功copy到sockfd的改善緩沖區的剩余空間後它就返回了,但是此時這些數據並不一定馬上被傳到連接的另一端。如果協議在後續的傳送過程中出現網絡錯誤的話,那麼下一個socket函數就會返回SOCKET_ERROR。(每一個除send的socket函數在執行的最開始總要先等待套接字的發送緩沖區中的數據被協議傳遞完畢才能繼續,如果在等待時出現網絡錯誤那麼該socket函數就返回SOCKET_ERROR)
7) 在unix系統下,如果send在等待協議傳送數據時網絡斷開,調用send的進程會接收到一個SIGPIPE信號,進程對該信號的處理是進程終止。
2.recv函數
sockfd: 接收端套接字描述符
buff: 用來存放recv函數接收到的數據的緩沖區
nbytes: 指明buff的長度
flags: 一般置為0
1) recv先等待s的發送緩沖區的數據被協議傳送完畢,如果協議在傳送sock的發送緩沖區中的數據時出現網絡錯誤,那麼recv函數返回SOCKET_ERROR
2) 如果套接字sockfd的發送緩沖區中沒有數據或者數據被協議成功發送完畢後,recv先檢查套接字sockfd的接收緩沖區,如果sockfd的接收緩沖區中沒有數據或者協議正在接收數據,那麼recv就一起等待,直到把數據接收完畢。當協議把數據接收完畢,recv函數就把s的接收緩沖區中的數據copy到buff中(注意協議接收到的數據可能大於buff的長度,所以在這種情況下要調用幾次recv函數才能把sockfd的接收緩沖區中的數據copy完。recv函數僅僅是copy數據,真正的接收數據是協議來完成的)
3) recv函數返回其實際copy的字節數,如果recv在copy時出錯,那麼它返回SOCKET_ERROR。如果recv函數在等待協議接收數據時網絡中斷了,那麼它返回0。
4) 在unix系統下,如果recv函數在等待協議接收數據時網絡斷開了,那麼調用 recv的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。 */
/*如果buf中沒有'\n',則用buf的真正長度strlen(buf)*/
else
len = send(newfd, buf, strlen(buf), 0);
if(len > 0)
printf("消息發送成功,本次共發送的字節數是:%d\n",len);
else{
printf("消息發送失敗!\n");
break;
}
/******接收消息*******/
bzero(buf, BUFLEN);
len = recv(newfd, buf, BUFLEN, 0);
if(len > 0)
printf("客戶端發來的信息是:%s,共有字節數是: %d\n",buf,len);
else{
if(len < 0 )
printf("接受消息失敗!\n");
else
printf("客戶端退出了,聊天終止!\n");
break;
}
}
/*關閉聊天的套接字*/
close(newfd);
/*是否退出服務器*/
printf("服務器是否退出程序:y->是;n->否? ");
bzero(buf, BUFLEN);
fgets(buf, BUFLEN, stdin);
if(!strncasecmp(buf, "y", 1)){
printf("server 退出!\n");
break;
}
}
/*關閉服務器的套接字*/
close(sockfd);
return 0;
}
client.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#define BUFLEN 10
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in s_addr;
socklen_t len;
unsigned int port;
char buf[BUFLEN];
/*建立socket*/
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(errno);
}
else
printf("socket create success!\n");
/*設置服務器端口*/
if(argv[2])
port = atoi(argv[2]);
else
port = 4567;
/*設置服務器ip*/
printf ("hello world.\n");
bzero(&s_addr, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(port);
if (0==inet_aton(argv[1], &s_addr.sin_addr))
{
perror(argv[1]);
exit(errno);
}
/*開始連接服務器*/
if(connect(sockfd, (struct sockaddr*)(&s_addr), sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(errno);
}
else
printf("conncet success!\n");
while(1)
{
/******接收消息*******/
bzero(buf, BUFLEN);
len = recv(sockfd, buf, BUFLEN, 0);
if(len > 0)
printf("服務器發來的消息是:%s,共有字節數是: %d\n", buf, len);
else{
if(len < 0 )
printf("接受消息失敗!\n");
else
printf("服務器退出了,聊天終止!\n");
break;
}
_retry:
/******發送消息*******/
bzero(buf, BUFLEN);
printf("請輸入發送給對方的消息:");
/*fgets函數:從流中讀取BUFLEN-1個字符*/
fgets(buf, BUFLEN, stdin);
/*打印發送的消息*/
//fputs(buf,stdout);
if(!strncasecmp(buf, "quit", 4))
{
printf("client 請求終止聊天!\n");
break;
}
/*如果輸入的字符串只有"\n",即回車,那麼請重新輸入*/
if(!strncmp(buf, "\n", 1))
{
printf("輸入的字符只有回車,這個是不正確的!!!\n");
goto _retry;
}
/*如果buf中含有'\n',那麼要用strlen(buf)-1,去掉'\n'*/
if(strchr(buf,'\n'))
len = send(sockfd,buf,strlen(buf)-1,0);
/*如果buf中沒有'\n',則用buf的真正長度strlen(buf)*/
else
len = send(sockfd, buf, strlen(buf), 0);
if(len > 0)
printf("消息發送成功,本次共發送的字節數是:%d\n",len);
else
{
printf("消息發送失敗!\n");
break;
}
}
/*關閉連接*/
close(sockfd);
return 0;
}