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

QQ登录

只需一步,快速开始

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 19173|回复: 0

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

[复制链接]
发表于 2020-5-9 02:09:24 | 显示全部楼层 |阅读模式
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西——socket(套接字)。; N4 Q+ z! i# G) E# U+ t
0 A8 \. m: T* C

% e/ @! H+ y5 t) c' x/ gsocket(套接字):简单来讲,socket就是用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
1 y/ `. j" v& G0 X4 J# y5 Y  j* |% A7 h8 y/ @% F
3 b0 W" Z  B1 Z
TCP协议7 D5 S: {+ [" A# a$ o6 y/ ]
TCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。" i. ?. b* D1 ^( {+ R3 K+ g3 ]
0 t: E# i8 s' u; }1 A" Z3 U
2 w! H5 J9 R6 g) |/ j; ~4 m* Q9 u
关键词:三次握手,可靠,基于字节流。
5 d, `/ w3 S7 L8 Z5 t
. r* [; d% ~# J1 [

. ~3 B9 M5 ^1 e5 }9 y; q5 G可能有朋友会问,TCP就这么简单一句话吗?当然不是,TCP作为非常重要的传输协议,细节知识是很多的,细讲起来这一篇文章怕是不够。不过在本篇内容中,我们只需了解他的几个关键词特性,就能很好的理解下面的内容。
% Q0 v5 H1 s! a; B: h 微信截图_20200509015654.png . h( h$ q, b' `4 @* C
TCP服务器端和客户端的运行流程0 f+ T) T% s+ N6 R& u

8 N; S# L9 z" {4 S8 B! p/ }$ `8 h
) ?; H$ Z/ ^# b1 S. G% Z
如图,这是一个完整的TCP服务器——客户端的运行流程图,其实我个人认为程序啊,不管哪个语言都是一样,核心就在于算法的设计和函数的调用。那么图中的函数都是什么意思呢?/ D$ A( V1 A) Z8 m. I
0 a7 N1 T* I( G

( `# w3 `$ [! Q6 J/ {# w1.创建socket
  D1 i" R0 T$ ^ socket是一个结构体,被创建在内核中
% j9 E! @% s( _6 Z% k sockfd=socket(AF_INET,SOCK_STREAM,0); //AF_INT:ipv4, SOCK_STREAM:tcp协议
- X; E! C* h' W5 Y& k( y% X
' Z! J, E3 K4 c  r: a5 m/ x
, C9 f( I% d' C& V9 l0 d
2.调用bind函数6 O4 t. }: b0 ^8 b3 S; p
将socket和地址(包括ip、port)绑定。  U2 z) ?! f# F: M: f. f- R" a1 x/ S
需要定义一个结构体地址,以便于将port的主机字节序转化成网络字节序
1 ^4 r, X9 a9 d) R2 X$ ~% Y struct sockaddr_in myaddr; //地址结构体
' x  }& f8 `& d, b1 A bind函数' }& u3 L& a6 S- B5 v
bind(sockfd,(struct sockaddr*)&myaddr,sizeof(serveraddr))
9 y, m" o2 K& p& V2 s6 @9 d9 d% z8 A1 s7 ]' m
. b4 L! m/ I5 _  l4 \5 r* ]
3.listen监听,将接收到的客户端连接放入队列
( R6 P, c" w/ ^& g0 u5 B. _8 t) o listen(sockfd,8) //第二个参数是队列长度
' }0 U+ t# n. V8 b, {1 E9 H5 A( p1 n8 t
4 V! R! U. w' B  G; c
4.调用accept函数,从队列获取请求,返回socket描 述符% T- O% r! }3 \  X
  如果无请求,将会阻塞,直到获得连接/ b4 d3 F) {; _' H
  int fd=accept(sockfd, NULL,NULL);//这边采用默认参数$ Q& U- _* G$ |& m6 F0 S& @
3 o# o6 L/ K+ S/ t) x: j$ D

- S( {2 W2 F& x. m7 n5.调用read/write进行双向通信2 L0 n3 x4 T. t6 W6 d6 v
" F1 k5 q' Z- ~4 z6 D- [0 d
* f  M/ n! N( b
6.关闭accept返回的socket6 O3 H$ T  z% y) R
  close(scokfd);
6 ]. U+ w4 }" [+ t2 T+ y5 V) q4 F1 T; B

. d! A8 p8 K1 k0 E- F" H" H7 g" j4 U
* n- R, }& T2 I1 a; e8 l8 s' ?
下面放出完整代码5 \8 |2 d: P$ [9 M
, e9 u  b( A/ t
  1. /*服务器*/0 Z7 ^; K, ?) ~3 J6 w2 Y' _
  2. #include <stdio.h>4 L0 M: m7 M! s) y1 x) [
  3. #include <string.h>
    8 ~, s5 W. N& |2 r( y( n' `( L8 Q
  4. #include <stdlib.h>0 S3 O% r3 K" C
  5. #include <strings.h>) b) r. a8 f4 Y& E8 V2 P2 N
  6. #include <sys/types.h>
    ) }5 `' X' e4 _% p% P9 L1 q
  7. #include <sys/socket.h>: Q" o: t# ?0 |/ r9 ?
  8. #include <arpa/inet.h>
    % _% c5 B1 u9 K( }5 v& _
  9. #include <netinet/in.h>
    8 m! z0 M- z8 a8 A0 h& ]: ]+ o
  10. int main()
    , p+ P  C! V9 t! _) d! P' d5 a4 O
  11. {
    . q  D3 b/ [. K9 E) d. B& O
  12.         int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
    / B* L4 M* m8 w6 @; y
  13.         if (sockfd < 0)
    * b3 y& k8 j+ Y2 d4 ]
  14.         {
    0 `; b6 \. F% F" @1 K$ ~9 b/ C
  15.                 perror("socket");
    5 }- H! ]& r' U" x4 i
  16.                 return -1;
    2 o# {1 q- Z0 B: X! j) p/ X$ J
  17.         } //创建失败的错误处理  Q' N) z( c7 ^% J
  18.          printf("socket..............
    0 t; u+ z$ k1 O2 w( P/ p4 W+ `; n& \
  19. "); //成功则打印“socket。。。。”: ^- B% f" @" ^9 P
  20.          
    # q1 }1 U) x. B
  21.          struct sockaddr_in myaddr; //创建“我的地址”结构体$ ?: _  r" B# ^& Q
  22.          memset(&myaddr, 0, sizeof(myaddr)); //对内存清零(保险起见)
    ! y& a+ `( U- b8 R3 m
  23.          myaddr.sin_family                 = AF_INET; //选择IPV4地址类型+ ^- X3 @" x0 R
  24.          myaddr.sin_port                 = htons(8888); //选择端口号+ I- @9 _0 t; X- S% v$ C$ G1 h
  25.          myaddr.sin_addr.s_addr         = inet_addr("192.168.3.169"); //选择IP地址
    7 c3 R7 G& s5 e8 ~" O( {1 Y
  26. 1 T: h. T9 S* ~" _  W
  27. if (0 > bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr)))//绑定套接字
    ' }+ ^! J5 p  v
  28.          {
    : t( p/ [/ N& y+ l- F( b
  29.                  perror("bind");
    ' G$ f# H7 q" ^$ J3 U
  30.                  return -1;
    - |: Z( [  @0 ]; ]& i7 T* U
  31.          }
    3 e( F1 A3 l9 n
  32.          printf("bind..........7 K7 d; z! l" `7 D% @  Q
  33. ");7 L% u" r. t. Z# k& z/ ^  W
  34. 3 m8 v% U1 |- s; m. j
  35.          if (0 > listen(sockfd, 8))//调用listen对指定端口进行监听
    9 g) s% E$ F: _- R1 j, w
  36.          {$ j! x; g) o0 r5 r" }2 Q0 c' d; t
  37.                  perror("listen");
    , Q3 q3 {# E3 V! t0 \' }3 ~
  38.                  return -1;
    4 I: H& }7 Y8 S' L. T& d, Z
  39.          }. t& R* \  i6 R, l
  40.          printf("listen............
    % `$ S) z+ E  g( p, A! f# |
  41. ");
    8 D; ^4 R& y9 O8 J$ R) s
  42.          
    ! {! z# o" [: w" E4 v6 O
  43.         int connfd = accept(sockfd, NULL, NULL);//使用accept从消息队列中获取请求
      X# j# i# w4 I: ]. l
  44.         if (connfd < 0)' C0 ?8 P, ~$ K$ }% N8 s# z
  45.         {& E0 c8 e# ^$ }. G
  46.                 perror("accept");
      ]3 V0 s0 w: d3 I  Y2 g# r* i2 t
  47.                 return -1;$ A- q3 r6 o) E0 L6 ?' [# {+ j( z
  48.         }3 {" R7 P3 \5 l" x8 S5 G) ?* d
  49.         printf("accept..............
    2 M4 }- L0 S5 U
  50. ");
    , b- i. q( u* E+ n: [: ?9 M+ r
  51.         char buf[100];//定义一个数组用来存储接收到的数据2 N% Q0 j' s: P% _8 P, [! r
  52.         int ret;
    8 p/ H4 |+ y$ X5 |. P/ E
  53.         while (1)& f5 @" a2 m9 p( J
  54.         {# |" V  `. T$ D9 C5 D- I
  55.                 memset(buf, 0, sizeof(buf));
    / f, K  g& A& P  {: b! P
  56.                 ret = read(connfd, buf, sizeof(buf));
    2 O* W( G: h/ n* g4 f; @0 h! k
  57.                 if (0 > ret)
    ! u6 b; x6 u! H- D8 Z# T
  58.                 {
    & L: }8 z6 D9 m6 k/ X- a  p4 s
  59.                         perror("read");: ]& J9 b: P( b3 Q# N- f
  60.                         break;3 A) [- X* A0 k" V4 n
  61.                 }//执行while循环读取数据,当
    # P$ D- g0 Z5 f, R" a# M
  62.                 else if (0 == ret)
    3 S% ~. }+ B+ A, f0 S
  63.                 {
      n. G6 ?3 J' p7 K. [
  64.                         printf("write close!
    ! ]/ ^2 r4 l% ~( _
  65. ");- x  T6 k  h% `+ q! ^
  66.                         break;+ ]7 Y/ o3 l+ i+ r) l& O0 @
  67.                 }
    7 N: t/ t* c" Z  D: S' f
  68.                 printf("recv: ");
    * y& p8 n' x* z! D' [' ~. L" O$ i
  69.                 fputs(buf, stdout);//打印接收到的数据# |2 V/ y- i; a+ B# ?! k% k6 |
  70.         }6 y( q$ K# `" r7 b6 g* b
  71.         close(sockfd);//关闭套接字
    5 Y6 C0 B" J/ d+ w; g9 ?
  72.         close(connfd);//断开连接- j: p5 O) }# P/ h) ?4 P
  73.         return 0;) d! o7 l$ K0 e& D' t# V  V
  74. }
复制代码
, u& O, W# o) H! q# w; M: k" {
$ y, ^6 S) W9 o6 b- J
  1. /*客户端*/(具体功能和服务器一样,所以不再加注释)
    8 D  I4 x: F' ^' x
  2. #include <stdio.h>0 Q8 A7 L# \7 f8 V: p
  3. #include <string.h>; y% r6 k% G; S+ x- F3 Y& e* U
  4. #include <stdlib.h>
    . u5 K0 a3 E$ h, w+ ], k
  5. #include <strings.h>
    % R- |  Y! s4 g7 M1 D
  6. #include <sys/types.h>5 L; H6 C! l& ]5 u6 n+ E) G
  7. #include <sys/socket.h>
    " F! x) \, m8 G( M, c: v+ N
  8. #include <netinet/in.h>. w* o. _. m" M. e
  9. #include <arpa/inet.h>
    % ]0 h, s! r! [1 r
  10. int main()
    $ v" e( b" d0 u! V, u# v
  11. {1 y( k6 K' S6 z) Y  v
  12. int sockfd;
      c' j4 s$ A. s- L! |: o8 n* N0 d
  13.         if (0 > (sockfd = socket(AF_INET, SOCK_STREAM, 0)))/ H* P5 O. S: R. W/ G: X) l+ @
  14.         {2 s! p' b( @; N. l# z
  15.                 perror("socket");
      ]5 T5 I: X: h4 N& g- U
  16.                 return -1;- [' X7 P# [' H1 L
  17.         }$ B3 ^1 k) T# G, M7 [* t6 y
  18.         printf("socket...........- P# l" t' ?0 x6 O( b* v) W
  19. ");5 S6 z5 b, C3 f* S1 e
  20.         ' o" I0 C( w* P
  21.         struct sockaddr_in srv_addr;
    % ~7 S# I1 {6 W* S' P) s, Y
  22.         memset(&srv_addr, 0, sizeof(srv_addr));) V) @/ i: I" t; l$ Z5 d/ A
  23.         srv_addr.sin_family                 = AF_INET;1 I# O- k( j- |8 B
  24.         srv_addr.sin_port                         = htons(8888);
    0 o' i% m" Z+ q/ J
  25.         srv_addr.sin_addr.s_addr         = inet_addr("192.168.3.169");
    / v9 x' f! _4 Q/ F+ I9 X
  26.         if (0 > connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))
    $ _% ~0 M) P; M2 ~2 V
  27.         {
    . B7 K) c* d. I: I
  28.                 perror("connect");3 V; z0 t, J2 F2 I0 E
  29.                 return -1; //exit //pthread_exit
    , |& f: G0 w# c! U$ h3 O
  30.         }
    # `; K: _: X( t3 G
  31.         printf("connect..............0 a1 k' s, a2 v- }8 I/ |7 g6 `& R
  32. ");- r/ N5 y' O0 W% W
  33.         char buf[100];- T# Z; S9 l2 ?! D6 D2 }" }
  34.         int ret;+ q5 O' s4 S2 e
  35.         while (1)
    " L8 t# w; j* {/ b9 j! y. k' W
  36.         {
    # a# q$ X7 e: n
  37.                 printf("send: ");2 f) S4 q4 D- z. d
  38.                 fgets(buf, sizeof(buf), stdin);
    9 p3 M. ~/ ?, ?/ w0 y7 Z
  39.                 ret = write(sockfd, buf, sizeof(buf));& B! K* V; j9 A- Z. Y# q  x
  40.                 if (ret < 0)& D0 I, w5 H7 \7 D
  41.                 {# z9 T3 Z: g, @/ g4 F0 c$ G( F
  42.                         perror("write");% T1 W  y2 @+ J0 z6 ^, d! O' g
  43.                         break;$ s7 m2 Q$ X) U
  44.                 }! M9 `$ U. D6 H5 ~* s3 y3 {
  45.                 if (strncmp(buf, "quit", 4) == 0). v% o5 A( s: k  o& f: v
  46.                         break;
    $ U9 X' z, ?7 p. E; g2 V3 H
  47.         }" V! I) I) L# Q8 q1 l5 j
  48.         close(sockfd);" H- E+ H: ^4 A+ P  W
  49.         return 0;
    , i2 I3 c$ M5 F  E) R) S5 G
  50. }
复制代码
+ W, C5 ]& g: p( t- k$ l

, ^0 e7 z8 v% D) M# \# j% @$ {) v
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

GMT+8, 2026-10-11 22:59 , Processed in 0.062091 second(s), 25 queries .

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