您尚未登录,请登录后浏览更多内容! 登录 | 立即注册

QQ登录

只需一步,快速开始

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 13343|回复: 0

[C] 自己动手用c语言写一个基于服务器和客户端(TCP)

[复制链接]
发表于 2020-5-9 02:09:24 | 显示全部楼层 |阅读模式
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西——socket(套接字)。
0 a- H& m) m1 T" \8 y3 A! N1 m- g6 M. R
" p) d1 o. d5 J/ V- Y5 N
socket(套接字):简单来讲,socket就是用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。7 h& q1 {' o& z! o6 j

) i5 C4 G2 O4 k. \/ \/ r

1 `5 {- S0 y" m/ o1 ?TCP协议
% F  r0 Z$ f* w$ w' D" s2 X0 r8 bTCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。5 w0 P& T. l" z
* d9 e% l7 t/ ]; k  S& Q
6 {3 O8 h6 u+ ]% G
关键词:三次握手,可靠,基于字节流。
* c  u$ q" U' `2 B3 P  a2 r6 O+ c8 c& A! z9 `, A0 t/ N
, I+ {# E7 }/ H( R6 U* c% h9 n4 i
可能有朋友会问,TCP就这么简单一句话吗?当然不是,TCP作为非常重要的传输协议,细节知识是很多的,细讲起来这一篇文章怕是不够。不过在本篇内容中,我们只需了解他的几个关键词特性,就能很好的理解下面的内容。
( _- a# V9 F2 B2 h. X 微信截图_20200509015654.png $ }9 O- l6 C2 d2 Z
TCP服务器端和客户端的运行流程4 u( z. a6 Y" P+ S
) |5 Y' k: M" u( g- X2 q' T' E
3 Q# h3 L4 x! [% {- W. j8 L
如图,这是一个完整的TCP服务器——客户端的运行流程图,其实我个人认为程序啊,不管哪个语言都是一样,核心就在于算法的设计和函数的调用。那么图中的函数都是什么意思呢?
+ Z/ Y! _$ ]7 L% K4 }- p* f  Y5 ?
) k, e8 v; R- f  V: k
) G7 B9 R) J1 M. t3 p- v
1.创建socket
; r/ D7 ~& P8 e1 q2 l% T3 G9 `0 y socket是一个结构体,被创建在内核中
( D: e" d: H% ~) t# M9 q- f sockfd=socket(AF_INET,SOCK_STREAM,0); //AF_INT:ipv4, SOCK_STREAM:tcp协议3 F& o" ~& X8 s( ?) D! E$ @- i- d
( i: U6 h" u5 o* E+ \1 Q$ t
4 {8 C  K. X! I& n+ J" ?
2.调用bind函数! i/ {5 {8 A5 ~8 g8 d- l4 \
将socket和地址(包括ip、port)绑定。7 s$ ~+ u, y/ _
需要定义一个结构体地址,以便于将port的主机字节序转化成网络字节序
- P' r5 T; q8 \2 W/ e struct sockaddr_in myaddr; //地址结构体
9 u& [6 W3 W; [4 i7 h8 C: v# L- d bind函数2 s" z( a4 o5 S* C# ~4 {
bind(sockfd,(struct sockaddr*)&myaddr,sizeof(serveraddr))
% ]4 m# S0 A1 \8 f- B
& n8 ^2 U  T+ [, ^
9 _$ W0 f4 q* c' b' b
3.listen监听,将接收到的客户端连接放入队列
9 C3 P& W0 C6 B3 u5 \( ?) \ listen(sockfd,8) //第二个参数是队列长度
" k& R& N$ T3 x5 e1 [6 V
- e% F/ U  s& w4 D2 I+ n) ?, T2 a
8 M( I6 c( o! S! r
4.调用accept函数,从队列获取请求,返回socket描 述符1 U/ x5 E: o1 H9 I
  如果无请求,将会阻塞,直到获得连接* L# M  c/ v' a
  int fd=accept(sockfd, NULL,NULL);//这边采用默认参数, D2 r1 K& m3 T/ s! q

% X6 S5 r% D9 S/ t) @7 L$ E
. \; l2 w3 F* A8 l& L" S0 e" y
5.调用read/write进行双向通信
" J3 Q4 j2 ~: F* ~' J
9 _: e" [7 t4 {9 z" G8 Q
% E5 W9 K( a) Z5 D3 h* ?$ j- p3 @, Q
6.关闭accept返回的socket7 r! f0 X* b! m5 u$ R# d0 l( i
  close(scokfd);# o9 z- P2 R4 p: ?+ M- _5 A3 q/ U
/ ]( Z$ R9 c& v# S
2 M" U$ Z( g# g5 b' G

3 q. c* L! V0 X9 w
2 I! ~0 D/ Y' a( P/ }) h$ l/ W
下面放出完整代码, v6 `4 ]! ]: J' q4 |, F* R6 X7 W

, ?. C. l) X+ X0 [( E
  1. /*服务器*/
    0 c# N$ n) l! V  j
  2. #include <stdio.h>
    * I/ }7 h1 L% u" N
  3. #include <string.h>
      P& s5 a2 e% j7 G( v. D
  4. #include <stdlib.h>
    . e, n& o# l, Y9 g7 X  h
  5. #include <strings.h>
    - q5 W- r% l4 y- u( _! ?! }% ?
  6. #include <sys/types.h>
    % V% B( R" r% d" F' b! g
  7. #include <sys/socket.h>
    1 I& z6 V* O; ?0 A% f8 g/ Z5 m; ]
  8. #include <arpa/inet.h>
    ; b" s( L6 l/ T7 x
  9. #include <netinet/in.h>& y  s2 z$ d) t1 f7 q' d+ d! Y
  10. int main()! J8 X7 E. O/ X7 N, h
  11. {
    : S3 T6 z5 E5 ^% V) D
  12.         int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
    0 |, y# L- V: K9 I% N6 x8 o+ ~' I
  13.         if (sockfd < 0)
    - D2 Y* `( L9 g8 H! Q& R
  14.         {/ M% q/ s5 v* b! \/ R7 r; s
  15.                 perror("socket");) ~' @+ c9 a+ D% `3 I" m7 \
  16.                 return -1;; _! D9 }  v2 s7 Q8 t
  17.         } //创建失败的错误处理
    - R( _3 Z! m) B
  18.          printf("socket..............$ p$ F, O+ g" p, T$ O3 J( m
  19. "); //成功则打印“socket。。。。”
    5 ?0 j, B8 Y( ~5 l0 g$ R# D6 m+ F
  20.          + K% i: d# _$ F* V9 r
  21.          struct sockaddr_in myaddr; //创建“我的地址”结构体. V, g7 [7 l! i; U6 \
  22.          memset(&myaddr, 0, sizeof(myaddr)); //对内存清零(保险起见)
    ( w1 k: r6 d! j* t/ N& q1 }' }
  23.          myaddr.sin_family                 = AF_INET; //选择IPV4地址类型0 f& ^  D2 d8 R4 J0 ^
  24.          myaddr.sin_port                 = htons(8888); //选择端口号
    3 K' p+ ^$ {7 T5 ^* M4 i
  25.          myaddr.sin_addr.s_addr         = inet_addr("192.168.3.169"); //选择IP地址7 p, F, ^0 a2 M3 ~$ n

  26. # m2 V# @( d7 [) A3 N
  27. if (0 > bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr)))//绑定套接字
    2 P8 X2 v* o4 I% G7 @( k) k
  28.          {6 y; ~) v% l/ {2 m, L0 ?
  29.                  perror("bind");. p! G6 ^* }5 |. p+ x
  30.                  return -1;
    ( j( z* y  H& ~& t* O( M: l
  31.          }
    ( }$ \$ `3 K$ d" x' e% V: Z
  32.          printf("bind..........8 u) d! U& f5 Q, S' `
  33. ");
    2 ~7 e! ]. A+ `5 T; L- K

  34. % ^' I# {5 z$ H& b5 o. b9 s
  35.          if (0 > listen(sockfd, 8))//调用listen对指定端口进行监听7 Z4 T1 M% b2 l) d
  36.          {
    * a& N5 O0 T$ `' K" C5 U2 n# \
  37.                  perror("listen");
    5 b. F$ U* z3 L0 a! D- f# Y6 S! k, j$ j
  38.                  return -1;) G* A1 j3 _& W( ]# |, Q
  39.          }
    8 W0 S, C6 Y) d
  40.          printf("listen............5 Y# b" s  ~, W' T
  41. ");
    , D5 x; N3 }) m2 H7 I* u% P4 ^
  42.            s/ k9 @2 {) J8 j$ F
  43.         int connfd = accept(sockfd, NULL, NULL);//使用accept从消息队列中获取请求% }" i) L: h  W! y# P* l& Q
  44.         if (connfd < 0)& |3 `  L# `2 I# q7 ]8 L% x6 g" H
  45.         {2 E0 h5 Y* |" h6 y! w
  46.                 perror("accept");( ?" ?+ S) |' K  l/ w' g6 a' [
  47.                 return -1;+ C" F. T  C5 W+ c1 e( X: q' ~  h
  48.         }6 t9 H2 J) c  e+ d& f( z
  49.         printf("accept..............
    ' c: L7 ?8 h8 @; A+ w8 D
  50. ");! x6 z1 y+ ]% ?1 m
  51.         char buf[100];//定义一个数组用来存储接收到的数据$ B; h$ x8 M5 I. o; b4 k$ {2 W
  52.         int ret;+ O2 Z; x; L5 @/ h# d0 L
  53.         while (1)% X4 j8 q/ H$ h9 t
  54.         {
    " a9 L) A9 |/ j  g) X; S; s
  55.                 memset(buf, 0, sizeof(buf));- _* R4 M! a6 }- n2 k
  56.                 ret = read(connfd, buf, sizeof(buf));7 W/ W5 m/ |) c( t  a) G
  57.                 if (0 > ret)4 i7 {8 C% l7 Y
  58.                 {" R/ E1 R- S, @( J, b# F
  59.                         perror("read");
    ' }5 S; P. a/ s" K  N- l
  60.                         break;1 ^) E* x1 J4 I, p8 v
  61.                 }//执行while循环读取数据,当( t5 Z+ A; `7 D1 y! E5 N
  62.                 else if (0 == ret)
    " c7 h! o$ s  e
  63.                 {2 q# L  E& J# V% j# [9 |
  64.                         printf("write close!
    5 C4 w6 }, u4 H1 w& L* ?/ `
  65. ");
    ' p' P1 v" @) H. d$ y  U% W
  66.                         break;
    / @+ i8 @2 z# u7 u6 y
  67.                 }( m5 C" Y3 {2 N- H4 V  ?# w# \$ k+ M
  68.                 printf("recv: ");8 v' y$ b2 f! ^
  69.                 fputs(buf, stdout);//打印接收到的数据
    & k  S- ^; A. N5 J
  70.         }: o/ q; \3 o' {  [2 E( \; Y
  71.         close(sockfd);//关闭套接字
    : J4 q5 M+ s" U
  72.         close(connfd);//断开连接
    0 D% @. C8 Q- |& @" P1 a
  73.         return 0;+ K4 j3 N8 j- U, \( y4 u
  74. }
复制代码
! Z6 q0 C9 M! ?3 F- N+ l

5 U+ N" y; \; b
  1. /*客户端*/(具体功能和服务器一样,所以不再加注释)
    & ~2 T+ e$ {! M( m# n& K7 A( S9 G
  2. #include <stdio.h>- H* Q6 Y* F5 C
  3. #include <string.h>
    6 I( a1 M- j2 g+ x2 ^2 a6 C  d2 A. m, @
  4. #include <stdlib.h>
    % G/ E- Z2 P2 [  a. T
  5. #include <strings.h>
    5 h) C3 g7 u+ Y
  6. #include <sys/types.h>7 X& y# M# l2 X0 g( G3 H
  7. #include <sys/socket.h>
    9 Z' }" n1 p: k2 V* R. W2 x. L: X
  8. #include <netinet/in.h>
    - f4 m5 ?# A; I4 J0 b+ U
  9. #include <arpa/inet.h>
    ! X& b6 A7 c. p- f8 O7 `$ z
  10. int main(). x, P2 X0 Q' j1 b/ t
  11. {
    0 r  m' E1 A0 b' T4 E; s6 C
  12. int sockfd;
    4 v4 X; {; c; `
  13.         if (0 > (sockfd = socket(AF_INET, SOCK_STREAM, 0)))" `& T! B; N' z' M# M
  14.         {
    5 i& ?& J) O0 o' S
  15.                 perror("socket");
    % l! {; p- S% Q; P. j
  16.                 return -1;
    8 v9 U8 q; e! b  E8 l) b, U8 s
  17.         }9 @  q7 L4 B4 L3 S
  18.         printf("socket...........# O: k: r5 ^8 I, q. }0 h
  19. ");
    1 ^! i0 R8 E# M8 k: I! [
  20.         * a: ]$ a: n2 L# d# W7 L% A
  21.         struct sockaddr_in srv_addr;
    0 Z4 H- b  q% j$ s- q* o2 q
  22.         memset(&srv_addr, 0, sizeof(srv_addr));$ l3 p( o9 A3 n3 J
  23.         srv_addr.sin_family                 = AF_INET;9 w. S$ W+ ~  H5 ^
  24.         srv_addr.sin_port                         = htons(8888);
    ' C* m; P/ `6 D3 j
  25.         srv_addr.sin_addr.s_addr         = inet_addr("192.168.3.169");
    6 {) D' i" h% z; ]( W
  26.         if (0 > connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))1 l* |" y. D$ Q/ I
  27.         {
    - d+ F. b/ r9 Q, t
  28.                 perror("connect");
    ' {1 x$ R' t. h3 P0 o2 j4 e
  29.                 return -1; //exit //pthread_exit) n/ ]/ Y" P( g0 `1 `" ?3 t
  30.         }
    " ]% b: a9 }: R7 l  f' K9 w& i
  31.         printf("connect..............) T* `1 L0 I1 \& C7 b
  32. ");
    - ?* H/ I( u+ y7 L  y' T2 E' G! @
  33.         char buf[100];* L+ O# m: O1 _+ M: K, L
  34.         int ret;
    - X  n2 @# S3 F' _5 ?0 [
  35.         while (1)+ k4 ^8 a/ @! L3 E% N: C
  36.         {* V; X' W$ {5 E
  37.                 printf("send: ");
    ) x, S. @' [5 B. \% I5 d
  38.                 fgets(buf, sizeof(buf), stdin);! H& K! G. g# }; m
  39.                 ret = write(sockfd, buf, sizeof(buf));
    4 N  {* m+ a4 _6 R% U$ n& T
  40.                 if (ret < 0)' z0 B3 |8 }5 d% A& i
  41.                 {
    ; C$ A) l3 Y. K+ M- E% [( j
  42.                         perror("write");
    0 ~4 d- U; {5 q, Y! B3 y2 q
  43.                         break;, |  Y2 x& [  m: F7 b& \
  44.                 }: J$ p" L$ W4 c
  45.                 if (strncmp(buf, "quit", 4) == 0)$ k1 l' @! o4 G" F( ^3 j0 K
  46.                         break;
      _/ i) r. L. p
  47.         }- M% r6 K& z2 S, C0 ?4 D7 C+ Q
  48.         close(sockfd);/ j4 r( c4 n* Z) @' l4 U
  49.         return 0;2 X; J5 V: z$ I
  50. }
复制代码

7 F& q2 f( M! Y! \+ V. _2 Q# `0 ~  u7 R& N, Z/ s
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

GMT+8, 2024-12-12 03:33 , Processed in 0.111695 second(s), 23 queries .

Copyright © 2001-2024 Powered by cncml! X3.2. Theme By cncml!