基於TCP(面向連接)的socket編程,分為服務器端和客戶端
服務器端的流程如下:
(1)創建套接字(socket)
(2)將套接字綁定到一個本地地址和端口上(bind)
(3)將套接字設為監聽模式,准備接收客戶端請求(listen)
(4)等待客戶請求到來;當請求到來後,接受連接請求,返回一個新的對應於此次連接的套接字(accept)
(5)用返回的套接字和客戶端進行通信(send/recv)
(6)返回,等待另一個客戶請求。
(7)關閉套接字。
客戶端的流程如下:
(1)創建套接字(socket)
(2)向服務器發出連接請求(connect)
(3)和服務器端進行通信(send/recv)
(4)關閉套接字
下面通過一個具體例子講解一下具體的過程和相關的函數,環境是suse linux.
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory.h>
#include <unistd.h>
//#include <linux/in.h>
#include <netinet/in.h>
//#include <linux/inet_diag.h>
#include <arpa/inet.h>
#include <signal.h>
/**
關於 sockaddr sockaddr_in socketaddr_un說明
http://maomaozaoyue.blog.sohu.com/197538359.html
*/
#define PORT 11910 //定義通信端口
#define BACKLOG 5 //定義偵聽隊列長度
#define buflen 1024
void process_conn_server(int s);
void sig_pipe(int signo);
int ss,sc; //ss為服務器socket描述符,sc為某一客戶端通信socket描述符
int main(int argc,char *argv[])
{
struct sockaddr_in server_addr; //存儲服務器端socket地址結構
struct sockaddr_in client_addr; //存儲客戶端 socket地址結構
int err; //返回值
pid_t pid; //分叉進行的ID
/*****************socket()***************/
ss = socket(AF_INET,SOCK_STREAM,0); //建立一個序列化的,可靠的,雙向連接的的字節流
if(ss<0)
{
printf("server : server socket create error\n");
return -1;
}
//注冊信號
sighandler_t ret;
ret = signal(SIGTSTP,sig_pipe);
if(SIG_ERR == ret)
{
printf("信號掛接失敗\n");
return -1;
}
else
printf("信號掛接成功\n");
/******************bind()****************/
//初始化地址結構
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET; //協議族
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址
server_addr.sin_port = htons(PORT);
err = bind(ss,(struct sockaddr *)&server_addr,sizeof(sockaddr));
if(err<0)
{
printf("server : bind error\n");
return -1;
}
/*****************listen()***************/
err = listen(ss,BACKLOG); //設置監聽的隊列大小
if(err < 0)
{
printf("server : listen error\n");
return -1;
}
/****************accept()***************/
/**
為類方便處理,我們使用兩個進程分別管理兩個處理:
1,服務器監聽新的連接請求;2,以建立連接的C/S實現通信
這兩個任務分別放在兩個進程中處理,為了防止失誤操作
在一個進程中關閉 偵聽套接字描述符 另一進程中關閉
客戶端連接套接字描述符。注只有當所有套接字全都關閉時
當前連接才能關閉,fork調用的時候父進程與子進程有相同的
套接字,總共兩套,兩套都關閉掉才能關閉這個套接字
*/
for(;;)
{
socklen_t addrlen = sizeof(client_addr);
//accept返回客戶端套接字描述符
sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen); //注,此處為了獲取返回值使用 指針做參數
if(sc < 0) //出錯
{
continue; //結束此次循環
}
else
{
printf("server : connected\n");
}
//創建一個子線程,用於與客戶端通信
pid = fork();
//fork 調用說明:子進程返回 0 ;父進程返回子進程 ID
if(pid == 0) //子進程,與客戶端通信
{
close(ss);
process_conn_server(sc);
}
else
{
close(sc);
}
}
}
/**
服務器對客戶端連接處理過程;先讀取從客戶端發送來的數據,
然後將接收到的數據的字節的個數發送到客戶端
*/
//通過套接字 s 與客戶端進行通信
void process_conn_server(int s)
{
ssize_t size = 0;
char buffer[buflen]; //定義數據緩沖區
for(;;)
{
//等待讀
for(size = 0;size == 0 ;size = read(s,buffer,buflen));
//輸出從客戶端接收到的數據
printf("%s",buffer);
//結束處理
if(strcmp(buffer,"quit") == 0)
{
close(s); //成功返回0,失敗返回-1
return ;
}
sprintf(buffer,"%d bytes altogether\n",size);
write(s,buffer,strlen(buffer)+1);
}
}
void sig_pipe(int signo)
{
printf("catch a signal\n");
if(signo == SIGTSTP)
{
printf("接收到 SIGTSTP 信號\n");
int ret1 = close(ss);
int ret2 = close(sc);
int ret = ret1>ret2?ret1:ret2;
if(ret == 0)
printf("成功 : 關閉套接字\n");
else if(ret ==-1 )
printf("失敗 : 未關閉套接字\n");
exit(1);
}
}
客戶端代碼:
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <memory.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h> //添加信號處理 防止向已斷開的連接通信
/**
信號處理順序說明:在Linux操作系統中某些狀況發生時,系統會向相關進程發送信號,
信號處理方式是:1,系統首先調用用戶在進程中注冊的函數,2,然後調用系統的默認
響應方式,此處我們可以注冊自己的信號處理函數,在連接斷開時執行
*/
#define PORT 11910
#define Buflen 1024
void process_conn_client(int s);
void sig_pipe(int signo); //用戶注冊的信號函數,接收的是信號值
int s; //全局變量 , 存儲套接字描述符
int main(int argc,char *argv[])
{
sockaddr_in server_addr;