TCP 服务端和客户端程序设计(C)

流程图

1 预备知识

1.1 socket函数

为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符

 #include <sys/socket.h>
 int socket(int family,int type,int protocol);    
      返回:非负描述字---成功   -1---失败
  • 第一个参数指明了协议簇,目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);
  • 第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口);
  • 如果套接口类型不是原始套接口,那么第三个参数就为0

1.2 connect函数

当用socket建立了套接口后,可以调用connect为这个套接字指明远程端的地址;如果是字节流套接口,connect就使用三次握手建立一个连接;如果是数据报套接口,connect仅指明远程端地址,而不向它发送任何数据

#include <sys/socket.h>      
  int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);  
           返回:0---成功   -1---失败

第一个参数是socket函数返回的套接口描述字;第二和第三个参数分别是一个指向套接口地址结构的指针和该结构的大小。
这些地址结构的名字均已“sockaddr_”开头,并以对应每个协议族的唯一后缀结束。以IPv4套接口地址结构为例,它以“sockaddr_in”命名,定义在头文件<netinet/in.h>;以下是结构体的内容:

struct in_addr {
 in_addr_t s_addr;     /* IPv4地址 */
}; 
struct sockaddr_in {
 uint8_t sin_len; /* 无符号的8位整数 */
 sa_family_t sin_family;
 /* 套接口地址结构的地址簇,这里为AF_INET */
 in_port_t sin_port; /* TCP或UDP端口 */
 struct in_addr sin_addr;
 char sin_zero[8];  
};

1.3 bind函数

为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。

#include <sys/socket.h>  
 int bind(int sockfd, const struct sockaddr * server, socklen_t addrlen);
 返回:0---成功   -1---失败 

第一个参数是socket函数返回的套接口描述字;
第二和第三个参数分别是一个指向特定于协议的地址结构的指针和该地址结构的长度。

1.4 listen函数

listen函数仅被TCP服务器调用,它的作用是将用sock创建的主动套接口转换成被动套接口,并等待来自客户端的连接请求。

#include <sys/socket.h>
 int listen(int sockfd,int backlog);   
 返回:0---成功   -1---失败

第一个参数是socket函数返回的套接口描述字;
第二个参数规定了内核为此套接口排队的最大连接个数。
由于listen函数第二个参数的原因,内核要维护两个队列:以完成连接队列和未完成连接队列。未完成队列中存放的是TCP连接的三路握手未完成的连接,accept函数是从已连接队列中取连接返回给进程;当以连接队列为空时,进程将进入睡眠状态。

1.5 accept函数

accept函数由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空,则进程进入睡眠状态。

#include <sys/socket.h>         
 int accept(int listenfd, struct sockaddr *client, socklen_t * addrlen);  
  回:非负描述字---成功   -1---失败

第一个参数是socket函数返回的套接口描述字;
第二个和第三个参数分别是一个指向连接方的套接口地址结构和该地址结构的长度;该函数返回的是一个全新的套接口描述字;如果对客户段的信息不感兴趣,可以将第二和第三个参数置为空。

1.6 write和read函数

当服务器和客户端的连接建立起来后,就可以进行数据传输了,服务器和客户端用各自的套接字描述符进行读/写操作。因为套接字描述符也是一种文件描述符,所以可以用文件读/写函数write()和read()进行接收和发送操作。
(1)write()函数用于数据的发送。

#include <unistd.h>         
 int write(int sockfd, char *buf, int len); 
  回:非负---成功   -1---失败

参数sockfd是套接字描述符,对于服务器是accept()函数返回的已连接套接字描述符,对于客户端是调用socket()函数返回的套接字描述符;
参数buf是指向一个用于发送信息的数据缓冲区;
len指明传送数据缓冲区的大小。
(2)read()函数用于数据的接收。

#include <unistd.h>         
 int read(int sockfd, char *buf, intlen);  
  回:非负---成功   -1---失败

参数sockfd是套接字描述符,对于服务器是accept()函数返回的已连接套接字描述符,对于客户端是调用socket()函数返回的套接字描述符;
参数buf是指向一个用于接收信息的数据缓冲区;
len指明接收数据缓冲区的大小。

1.7 send和recv函数

TCP套接字提供了send()和recv()函数,用来发送和接收操作。这两个函数与write()和read()函数很相似,只是多了一个附加的参数。
(1)send()函数用于数据的发送。

#include <sys/types.h>
#include < sys/socket.h >         
ssize_t send(int sockfd, const void *buf, size_t len, int flags);  
  回:返回写出的字节数---成功   -1---失败

前3个参数与write()相同,参数flags是传输控制标志。
(2)recv()函数用于数据的发送。

#include <sys/types.h>
#include < sys/socket.h >         
ssize_t recv(int sockfd, void *buf, size_t len, int flags);  
  回:返回读入的字节数---成功   -1---失败

前3个参数与read()相同,参数flags是传输控制标志。

2 服务器端

2.1 源码

#include <stdio.h>  
       #include <stdlib.h>  
       #include <string.h>  
       #include <unistd.h>  
       #include <sys/types.h>  
       #include <sys/socket.h>  
       #include <netinet/in.h>  
       #include <arpa/inet.h>  
       
       #define  PORT 1234  
       #define  BACKLOG 1  
   
       int main()  
       {  
       int  listenfd, connectfd;  
       struct  sockaddr_in server;  
       struct  sockaddr_in client;  
       socklen_t  addrlen;  
       if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)  
       {  
       perror("Creating  socket failed.");  
       exit(1);  
       }  
       int opt =SO_REUSEADDR;  
       setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  
       bzero(&server,sizeof(server));  
       server.sin_family=AF_INET;  
       server.sin_port=htons(PORT);  
       server.sin_addr.s_addr= htonl (INADDR_ANY);  
       if(bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) {  
       perror("Binderror.");  
       exit(1);  
       }     
       if(listen(listenfd,BACKLOG)== -1){  /* calls listen() */  
       perror("listen()error\n");  
       exit(1);  
       }  
       addrlen =sizeof(client);  
       if((connectfd = accept(listenfd,(struct sockaddr*)&client,&addrlen))==-1) {  
       perror("accept()error\n");  
       exit(1);  
       }  
       printf("Yougot a connection from cient's ip is %s, prot is %d\n",inet_ntoa(client.sin_addr),htons(client.sin_port));  
       send(connectfd,"Welcometo my server.\n",22,0);  
       close(connectfd);  
       close(listenfd);  
return 0;  
       }  

2.2 编译运行

mkdir tcp_learn
cd tcp_learn/
vim tcpserver.c

粘贴入源码,然后:

gcc -o tcpserver tcpserver.c        //编译
./tcpserver             //运行

暂时还没现象,客户端还没连接进来。

3 客户端

3.1 源码

#include<stdio.h>  
       #include <stdlib.h>  
       #include<unistd.h>  
       #include<string.h>  
       #include<sys/types.h>  
       #include<sys/socket.h>  
       #include<netinet/in.h>  
       #include<netdb.h>  
   
       #define  PORT 1234  
       #define  MAXDATASIZE 100  
   
       int main(int argc, char *argv[])  
       {  
       int  sockfd, num;  
       char  buf[MAXDATASIZE];  
       struct hostent *he;  
       struct sockaddr_in server;  
       if (argc!=2) {  
       printf("Usage:%s <IP Address>\n",argv[0]);  
       exit(1);  
       }  
       if((he=gethostbyname(argv[1]))==NULL){  
       printf("gethostbyname()error\n");  
       exit(1);  
       }  
       if((sockfd=socket(AF_INET, SOCK_STREAM, 0))==-1){  
       printf("socket()error\n");  
       exit(1);  
       }  
       bzero(&server,sizeof(server));  
       server.sin_family= AF_INET;  
       server.sin_port = htons(PORT);  
       server.sin_addr =*((struct in_addr *)he->h_addr);  
       if(connect(sockfd,(struct sockaddr *)&server,sizeof(server))==-1){  
       printf("connect()error\n");  
       exit(1);  
       }  
       if((num=recv(sockfd,buf,MAXDATASIZE,0)) == -1){  
       printf("recv() error\n");  
       exit(1);  
       }  
       buf[num-1]='\0';  
       printf("Server Message: %s\n",buf);  
       close(sockfd);  
return 0;  
}  

3.2 编译运行

新打开一个命令窗口:

cd tcp_learn/
vim tcpclient.c

粘贴源码,然后运行:

gcc -o tcpclient tcpclient.c
./tcpclient 127.0.0.1

4 结果

TCP
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容