如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西——socket(套接字)。
7 b+ `. J, u1 ]& P( U+ @" q5 l% |- \ q3 x1 } @
6 q4 }$ C9 ^9 ^- g3 G# Xsocket(套接字):简单来讲,socket就是用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
5 z9 h% X9 f$ i I( z1 \
6 e0 O/ T$ L* |0 U; B9 R8 b- y& d0 @. z
TCP协议8 _9 e) w6 y+ N4 ]- ^
TCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。2 ` I. U7 G" D& e7 q5 j7 Q$ v9 f
& y7 E! R8 E4 e% }" }/ S0 b5 z& x. I2 X4 {; u) S4 T0 W( W4 j4 F, |
关键词:三次握手,可靠,基于字节流。
, V( h5 ^) ~- ~. @
# v( x4 n8 C5 f6 a c9 D) Q1 U; z. H4 L- T& v1 ], z! v
可能有朋友会问,TCP就这么简单一句话吗?当然不是,TCP作为非常重要的传输协议,细节知识是很多的,细讲起来这一篇文章怕是不够。不过在本篇内容中,我们只需了解他的几个关键词特性,就能很好的理解下面的内容。' B! \# w5 L( [" ^
7 ]' ]1 {) o, N$ P* Q5 r; F
TCP服务器端和客户端的运行流程
* Z* k1 D* e2 N0 d7 Y2 e* T# S, `) Z. n
% G) }! l; {7 v, x C# A如图,这是一个完整的TCP服务器——客户端的运行流程图,其实我个人认为程序啊,不管哪个语言都是一样,核心就在于算法的设计和函数的调用。那么图中的函数都是什么意思呢?
% I+ H' {0 s) s! h: R( _ i1 ~
9 y, T% ~: N7 y. j3 ^1 C: V* G# [6 B( R
1.创建socket$ @3 I# b% _; ]5 \* c Z
socket是一个结构体,被创建在内核中! T9 _7 v* q& g) V7 p% R
sockfd=socket(AF_INET,SOCK_STREAM,0); //AF_INT:ipv4, SOCK_STREAM:tcp协议
8 H0 n, J, ^/ ]3 U* ]& x i& w, C" S1 T. A$ m
) }+ W9 B% _+ \! O1 s# M
2.调用bind函数
; R+ t9 h! Q, q8 t 将socket和地址(包括ip、port)绑定。
N) a: R# S- a o3 G% N7 a% F i Y 需要定义一个结构体地址,以便于将port的主机字节序转化成网络字节序+ x( v* A9 S( _5 X% @
struct sockaddr_in myaddr; //地址结构体
9 R% [% O1 h( F2 U% z) ?: ` bind函数
+ Z2 n& g, H6 X; q bind(sockfd,(struct sockaddr*)&myaddr,sizeof(serveraddr))
0 P$ W0 \9 f( e! ?9 ^ i
. W$ V6 [& F- @' K3 y( {' M$ V5 B- y/ ~+ b! \$ } H; {
3.listen监听,将接收到的客户端连接放入队列
0 e4 g# m: S. ?. B" X listen(sockfd,8) //第二个参数是队列长度3 [- @( [9 V1 P7 ?( F
( ^$ ^9 e B* @" E" q! i( o* f1 f- e+ V
4.调用accept函数,从队列获取请求,返回socket描 述符- N! `3 H c1 Y
如果无请求,将会阻塞,直到获得连接: a+ v" M* z' x* H0 `' {2 o3 v
int fd=accept(sockfd, NULL,NULL);//这边采用默认参数
. R# R6 k; Y2 z" v% }9 B
! F% i* a; w$ S! E+ M
, @: V8 o3 j) w" O% w5.调用read/write进行双向通信# k( e# B5 M! J3 N6 N, D J! K2 s% A
% [- Z( F/ y; u5 x3 z: p
7 r" M) X5 K$ k' l) v& Q* p: E6.关闭accept返回的socket
+ f- W7 r% @. r/ k. D) ? close(scokfd);
+ Z5 B+ [- Q( j3 x
! _+ O9 Y _2 I& J
9 s1 x) \8 W' w# U$ u6 b
' @: j- b, M! J; V( A! |6 ]7 v+ }" E4 z# x* q
下面放出完整代码
4 i6 b3 r: L: ~0 y
1 n/ ?2 ~+ c/ t) L. f- /*服务器*/8 y7 x& B* Q" v* Y k/ m) n: y3 U
- #include <stdio.h>
: J8 | k( n2 @5 o6 e - #include <string.h>+ I5 }/ y9 Y4 s$ ~
- #include <stdlib.h>" r1 P+ r+ N( W' N0 P$ I' e
- #include <strings.h>
+ m1 z# F/ p2 Q- l" a1 V9 m - #include <sys/types.h># x( Z l1 o |! {
- #include <sys/socket.h>
0 j6 B0 a" Z- R5 } ?# _0 R - #include <arpa/inet.h>
" L4 h& ^$ `/ n; ^- _ - #include <netinet/in.h> ]. t; A1 {, V h# O. y4 ^
- int main()* h; R7 c- N9 b; j
- {4 f! Q/ f7 Y! h9 e8 k% M
- int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字2 g5 m- ]! T# R4 j
- if (sockfd < 0)+ ~: ~" l/ x3 W, M
- {
5 ^% ^# q) Q4 M7 q - perror("socket");
) y& ^' r6 X$ b% R5 t2 [ - return -1;0 D3 o1 l/ E( r, A
- } //创建失败的错误处理4 g9 r' V9 A! E" m- I: V
- printf("socket..............
% a, |3 t" @5 ` - "); //成功则打印“socket。。。。”! F" i4 U; V7 q' j6 i" }. E' k
- 1 n& a4 c+ M% ^0 P* _5 g1 s7 D. t% C
- struct sockaddr_in myaddr; //创建“我的地址”结构体
+ s$ B7 ~3 h3 c- `2 Z - memset(&myaddr, 0, sizeof(myaddr)); //对内存清零(保险起见). m* f4 c- }. T# f) _. w3 H- h
- myaddr.sin_family = AF_INET; //选择IPV4地址类型' ?# i4 v% z' N+ Z- N7 `
- myaddr.sin_port = htons(8888); //选择端口号
0 T7 b2 P0 E8 I) I8 n - myaddr.sin_addr.s_addr = inet_addr("192.168.3.169"); //选择IP地址
* z7 ^+ Z, t; @& }$ q+ v -
3 M9 L' B6 h$ G6 ^ n( k. O - if (0 > bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr)))//绑定套接字
% @% D% B9 ~( G" M7 s* C/ Z - {
4 \ d$ d' o$ [" R m) M1 v - perror("bind");
* t3 D+ L2 w7 I; d& ~ - return -1;" S% o/ f9 P, o | g' P- v& y
- } H. H/ o) T4 G- R0 k* n* t
- printf("bind..........
9 l4 `; M4 P' h q3 e& z# E/ V - ");
' C5 D- I0 r* o8 ` -
3 w6 q l0 o, z - if (0 > listen(sockfd, 8))//调用listen对指定端口进行监听
; A- X. ?3 ]) w - {4 c" \( l0 @5 S
- perror("listen");
* @( Z8 R: G5 p& z( Q4 q - return -1;
8 M0 I6 k( q; ~8 Y/ Q - }
% t- _+ ?' L( R2 t/ s- [# z - printf("listen............
* h) M6 U) }/ t! o; h" O - ");% T* H+ \2 s) E6 c/ Q8 X" H
-
0 y' Y! g/ U# F* [4 M% L - int connfd = accept(sockfd, NULL, NULL);//使用accept从消息队列中获取请求
1 N! P6 r1 w- I5 h# N3 K - if (connfd < 0)
7 L+ E/ b( o" P& C8 q! D - {
' u0 s/ c; w: Z+ `% ?; U" o - perror("accept");
' O y( ]- ]' B9 ~' g - return -1;
1 C) v, V5 ]- W/ p7 T - }
$ M) }" e+ v* ]: n - printf("accept..............( d* P- W( h/ n; \
- ");
6 f+ y ]) \0 A - char buf[100];//定义一个数组用来存储接收到的数据
0 t+ C: @4 T. }# x9 P - int ret;
' V1 [! ]4 o. T) M - while (1)
% A* U, }; V: U. ~* F9 v6 ^ - {
( n, i7 {+ H, V* @+ y$ G - memset(buf, 0, sizeof(buf));" ^( @8 K9 Q7 T: y7 v I3 K8 o
- ret = read(connfd, buf, sizeof(buf));
5 d5 Q5 ?+ m8 q: O5 I/ P7 \ - if (0 > ret); T# b4 v" _: [% @0 o
- {
. b7 G; a, X3 n3 u' r8 ]! A# w t - perror("read");
& S- V8 C3 j4 W1 S9 [0 `2 N# V - break;
) s$ a1 M5 l/ F - }//执行while循环读取数据,当
. q8 B2 C7 i7 R# ?, {& j5 Y - else if (0 == ret): R" I! I" r2 W G- f& p+ u; P- M' T
- {. N# r7 C ^7 F( ]4 w6 B" s* G
- printf("write close!
3 ~% Z/ e8 B" W. f* _- ^/ r6 V - ");8 H' _1 }( g7 h9 @8 d* V/ s
- break;' f- p2 k* o# N6 I- q
- } e' S1 r! T" s; L
- printf("recv: ");
7 j) r" j5 f5 p! O+ j6 P | - fputs(buf, stdout);//打印接收到的数据
) t& C+ D4 ?7 y, s/ t - }# B1 i! ~- i3 z& S' J
- close(sockfd);//关闭套接字
5 r; k; S3 ]+ [5 H' e: b$ j - close(connfd);//断开连接* G) T7 m. v' ]0 @+ v+ B9 ^9 A
- return 0;& L. z Q# s8 e; O( X4 E8 H; \
- }
复制代码
$ K3 e' g7 w1 {7 c- D5 G- q- ^/ N( K0 R; b9 |: w
- /*客户端*/(具体功能和服务器一样,所以不再加注释)
; J7 o9 [( x" X: ?7 v7 O - #include <stdio.h>3 M* l1 W& i; n- |+ X! U" f& v/ G4 v
- #include <string.h>4 G! k0 y4 M5 H3 a
- #include <stdlib.h>7 `) {" j6 H8 W1 Q1 h; O& I7 x
- #include <strings.h>& b% x6 I, W: {1 B" a, A
- #include <sys/types.h> v. u2 c9 E0 D+ ^5 M G1 X
- #include <sys/socket.h>
6 r- U+ T \+ z - #include <netinet/in.h>1 D/ k9 u h: C1 u$ R4 N
- #include <arpa/inet.h>
, W6 D0 Z) m4 z k8 ^0 p" |& g: e - int main()
$ }7 Q% i# O3 L; k' p( G! c - {' _# d* G. p" D/ [& J) s
- int sockfd;
5 a: y+ j) \3 G2 F3 P0 t - if (0 > (sockfd = socket(AF_INET, SOCK_STREAM, 0)))8 k3 v. |. N% B: B5 _& N2 U
- {
6 C; t& t* b2 n$ z' U( t - perror("socket");/ d. S3 G& j$ }6 U
- return -1;. U- U% M3 l' q; t
- }$ \8 c* a5 ]3 B+ ^' N" t' O5 j4 U1 d
- printf("socket...........
+ C: t. x' O6 u9 @; g - ");
; k- |4 [% E! t. c -
' }) E+ p; N9 Z - struct sockaddr_in srv_addr;# A. X: r" Z) J3 L
- memset(&srv_addr, 0, sizeof(srv_addr));2 ~0 d9 @* l) C! ~( [) y
- srv_addr.sin_family = AF_INET;
+ b8 p* N5 W. c$ ]7 c8 C4 s6 P/ ]3 @ - srv_addr.sin_port = htons(8888);" l F' w" M7 g- A9 k4 O# M
- srv_addr.sin_addr.s_addr = inet_addr("192.168.3.169");: f7 b8 x8 ]5 c% ~8 {& T
- if (0 > connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))# `; O# j; l4 l W- W9 H
- {
% i# M2 B* @) o8 { - perror("connect");$ H }1 O) {1 u6 _4 U! c: p+ ~8 B: H
- return -1; //exit //pthread_exit
( X9 H8 z! C0 X. C - }
, l# S/ k7 Q- B& J1 _/ W. L - printf("connect..............2 H7 y3 d9 o5 O# B$ B1 H K3 Y4 e
- ");, ~: g* d! z7 p0 U4 A
- char buf[100];+ q* A( X) }' `- c9 R
- int ret;
' n% ?8 _# R8 z2 F - while (1). L, d* \" x" }- m0 }5 ^5 N
- {- ^1 c+ v2 I% q
- printf("send: ");
# j5 j# P! ~8 s - fgets(buf, sizeof(buf), stdin);
% ?& ?! s0 K7 k5 |9 P/ G - ret = write(sockfd, buf, sizeof(buf));! B: g7 w7 l4 \6 F3 p) L G
- if (ret < 0)
1 @& w- `* [0 A, O3 X; B& L* G - {
1 ^- }4 m6 E' B( q9 Q+ N0 ] - perror("write");) n8 h+ \3 k% O8 U, ?* t* _
- break;0 r+ f% i' K0 Z4 U( l
- }
; v Z3 w; v0 p/ F - if (strncmp(buf, "quit", 4) == 0)0 P" D: v/ G q. o0 ^# e4 R
- break;, ^' v! w( R6 }9 r1 P
- }
5 S, i$ T/ |# i2 [/ r6 e! N - close(sockfd);3 I$ Z/ y {! C9 R& T7 o
- return 0;6 s; Z9 d: O: A
- }
复制代码
" G0 \$ K; ^8 ]$ F7 g- z" Q) x( F# O1 c& Z S0 G& L
|