#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
static void usage(const char* proc)
{
printf(\”Usage: %s [local_ip] [local_port]\”,proc);//提示輸入出錯,應該輸入./tcp_servet ip地址 端口號(運行服務器)
}
int startup(const char* ip,int port)//創建套接字
{
int sock = socket(AF_INET,SOCK_STREAM,0);//socket()創建套接字函數
if(sock<0)
{
perror(\”socket\”);//提示創建套接字失敗
exit(2);
}
//socket的返回值只是一個文件描述符,為了進行網絡通信,我們還需將其與服務器 ip地址和端口號進行綁定
struct sockaddr_in local;//結構體struct sockaddr_in是struct sockaddr的具體化
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(sock,(struct sockaddr*) &local,sizeof(local))<0){//bind(綁定函數
perror(\”bind\”);
exit(3);
}
//監聽
if(listen(sock,10)<0)//服務器24小時監聽,等待連接請求
{
perror(\”listen\”);
exit(4);
}
return sock;
}
//Version 1
//./tcp_servet 127.0.0.1 8080 本地環回
int main(int argc,char* argv[])
{
if(argc!=3){
usage(argv[0]);
return 1;
}
int listen_sock = startup(argv[1],atoi(argv[2]));//create socket
int new_sock;
while(1){
struct sockaddr_in client;
socklen_t len = sizeof(client);
new_sock = accept(listen_sock,(struct sockaddr*)&client ,&len);//accept創建新的socket,用來處理客戶段請求,listen_sock只負責監聽
if(new_sock<0)
{
perror(\”accept\”);
exit(5);
continue;
}
//成功獲取到客戶端,打印客戶端的 ip和端口號,accept的第二個參數是輸出型參數,由它可得客戶端的ip和端口號
printf(\”get an new client: %s, %d\\n\”,inet_ntoa(client.sin_addr),ntohs(client.sin_port));
break;
}
//客戶端和服務器端的數據傳輸
char buf[1024];
while(1){
int r = read(new_sock,buf,sizeof(buf)-1);//從網絡中讀取數據(客戶端發送數據)放入buf中;
if(r<0){//讀取失敗
perror(\”read\”);
close(new_sock);
break;
}else if(r==0){//客戶端已關閉
close(new_sock);
printf(\”client qiuit…\\n\”);
break;
}
//讀取成功
buf[r] = 0;
printf(\”clinet: %s\\n\”,buf);
write(new_sock,buf,strlen(buf));//回送數據
}
return 0;
}
tcp_client.c客戶端
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
void static Usage(const char* proc)//提示打印格式
{
printf(\”Usage: %s [local_ip] [local_port]\\n\”,proc);//運行客戶端./tcp_servet 服務器ip 服務器端口號
}
//./tcp_clinet servet_ip servet_port
int main(int argc,char* argv[])
{
if(argc!=3){
Usage(argv[1]);
exit(1);
}
int sock = socket(AF_INET,SOCK_STREAM,0);//創建套接字
if(sock < 0 ){
perror(\”socket\”);
exit(2);
}
//客戶端socket不綁定
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));//htons將本地端口號轉為網絡端口號
server.sin_addr.s_addr = inet_addr(argv[1]); //給數據結構成員賦值
//connect()鏈接服務器
if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0){
perror(\”connect\”);
exit(3);
}
//客戶端發送數據
char buf[1024];
while(1){
printf(\”Please Enter$\”);
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);//從標準輸入讀數據到buf
if(s>0){//讀取成功
buf[s-1] = 0;
write(sock,buf,strlen(buf));//將數據寫到網絡中
s = read(sock,buf,sizeof(buf)-1);//讀取回顯數據
if(s>0){
buf[s] = 0;
printf(\”servetr echo#:%s\\n\”,buf);
}
}
return 0;
}
注意要點:
上述的服務器地址采用127.0.0.1,故該服務器沒有通過網絡通信,還是一直監聽所在主機上的客戶鏈接請求,這是本地環回;
該客戶/服務器是一個單進程的服務器,一次只能鏈接一個客戶請求,因為我們在收到一個客戶請求后,執行完客戶端的請求才接受下一個請求的鏈接
客戶端的套接字可以綁定也可以不綁定,但一般不進行綁定,因為一點綁定服務器的ip,那么若別的客戶端想鏈接,鏈接不上
涉及函數以及命令
創建socket
domain表示域,ipv4為AF_INET;
type表示類型,SOCK_STREAM(面向字節流)
protocal表示協議,單個通信協議情況下為0
bind綁定
sockfd : socket()的返回值,一個文件描述符
addr:一個struct sockaddr_in 的結構體
struct in_addr結構體成員是s_addr,表示ip地址;
addrlen:結構體大小
accept創建新的socket
addr是一個輸出型參數,輸出的是客戶端的struct sockaddr_in 的內容
addrlen是一個輸入輸出型參數
ip轉換
本地端口號與網絡端口號的互換
監聽
backlog : 是操作系統底層連接隊列,不能設置過大;
connect函數(客戶端連接服務器)
2.多進程服務器編寫
代碼清單:
tcp_servet.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
//Version2
void static usage(const char* proc)
{
printf(\”Usage: %s [local_ip] [local_port]\\n\”,proc);
}
int startup(char* _ip,int _port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<0){
perror(\”socket\”);
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip);
if(bind(sock,(struct sockaddr*) &local,sizeof(local))<0){
perror(\”bind\”);
exit(3);
}
if(listen(sock,10)<0){
perror(\”listen\”);
exit(4);
}
return sock;
}
//./tcp_servet local_ip local_port
int main(int argc,char* argv[]){
if(argc!=3){
usage(argv[0]);
return 1;
}
int listen_sock = startup(argv[1],atoi(argv[2]));
int new_sock;
while(1){
struct sockaddr_in client;
socklen_t len = sizeof(client);
new_sock = accept(listen_sock,(struct sockaddr*) &client,&len);
if(new_sock < 0){
perror(\”accept\”);
exit(5);
continue;
}
printf(\”get an new client:%s,%d\\n\”,inet_ntoa(client.sin_addr),ntohs(client.sin_port));
break;
}
//創建了一個子進程代替我們處理客戶端的請求—與上述單進程的不同之處
pid_t id = fork();
if(id<0){
perror(\”fork\”);
exit(6);
}else if(id==0){//child
close(listen_sock);//關閉不必要的文件
if(fork > 0){//兩次fork
exit(7);
}
char buf[1024];
while(1){
int r= read(new_sock);
if(r<0){
perror(\”read\”);
close(new_sock);
break;
}else if(r==0){
close(new_sock);
printf(\”clinet quit…\\n\”);
break;
}
buf[r] = 0;
printf(\”clien: %S\\n\”,buf);
write(new_sock,buf,strlen(buf));
close(new_sock);
}
}else{//father
close(new_sock);//父進程繼續負責監聽客戶請求,必須關閉new_sock文件
}
}
注意要點:
與單進程的唯一不同的是, fork了一個子進程,每次客戶端的請求交給子進程完成,父進程只負責監聽 ;
兩次 fork,為了能夠讓父進程只負責監聽,而不阻塞式的等待子進程的退出,我們采用兩次fork,此時存在兩個進程,兩個進程存在爺孫關系,“孫子”進程此時由于父進程已經退出,所以它是孤兒進程,由 init進程回收,與此刻的父進程沒有關系
pid_t id = fork();
if(id<0){
perror(\”fork\”);
exit(6);
}else if(id==0){//child
close(listen_sock);//關閉不必要的文件
if(fork > 0){//兩次fork,第二次創建完子進程后該父進程退出
exit(7);
}
父進程必須關閉new_sock文件,因為子進程拷貝了父進程的文件描述符表,兩者表是一樣的,若不關閉,每次客戶端發送來一個新的連接,就要在父進程的文件描述符表中打開一個文件,這樣浪費資源,而且很快會用完;(子進程處理完信息后退出時關閉了文件描述符表,而父進程一直處于運行狀態,所以必須主動關閉)
3.多線程服務器的編寫
代碼:
更多關于云服務器,域名注冊,虛擬主機的問題,請訪問三五互聯官網:www.shinetop.cn