首页 > 技术文章 > 网络编程1 网络开发基础(Socket、TCP、UDP)

ccsuCBG 2018-11-01 00:06 原文

OSI七层网络模型

1.网络的七层结构:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

2.在网络通信的发送端,其通信数据每到一个通信层,都会被该层协议在数据中添加一个包头数据。而在接收方恰恰相反,数据通过每一层时都会被该层协议剥去相应的包头数据。

 

TCP/IP

1.IP地址

IP地址是指互联网协议地址(Internet Protocol Address,又译为网际协议地址)。IP地址时IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一个主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

2.Port

端口号:为了区分不同的网络程序,计算机会为每一个· 网络程序分配一个独一无二的端口号,端口是一个虚拟的、逻辑上的概念。

for example:Web服务的端口号是80,FTP服务的端口号是21,SMTP服务的端口号是25。

3.TCP/IP通信协议的网络层次结构

应用层 运输层 网际层IP 网络接口层

4.TCP/IP协议的特性

1.封包交换网络服务

2.可靠流传输服务

3.独立网络技术

4.通用互连

5.端到端应答式

6.标准应用协议

5.TCP/IP中的各种协议

1.IP协议

IP协议是网络层协议,主要职责是把数据从源地址传送到目的地址,并提供两个基本功能--寻址和分段

IP协议提供的是不可靠无连接的服务

2.TCP协议

TCP协议是面向连接的通信传输控制协议,其提供两个计算机之间的可靠无错的数据传输,三报文握手建立TCP连接

3.UDP协议

UDP是面向无连接通信的用户数据报协议,UDP不保障可靠的数据传输,但能够向若干个目标发送数据,接受多个源地址的数据

4.IP协议位于网络层,TCP\UDP协议对应于传输层、而HTTP协议对应于应用层。TCP/IP协议是传输层协议,主要解决数据如何在网络中传输;HTTP是应用层协议,主要解决如何包装数据。

 

C/S编程模型

1.Client/Serve

2.C/S编程模型是基于可靠连接的通信模型,在通信的双方必须使用各自的IP地址以及端口进行通信。

3.服务端等待客户端连接请求的到来的过程称为监听过程

4.C/S编程模型如下:

1.客户端向服务器发出连接请求
2.服务器应答客户端的请求
3.服务器与客户端之间进行数据交换请求
4.客户端关闭与服务器之间的连接

Socket简介

1.Socket是一个软件结构,被客户程序或服务进程用来发送和接受信息
所谓的Socket,是指TCP/IP协议网络的API。Socket接口中定义了许多函数和例程,用来开发TCP/IP协议网络上的应用程序

2.socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

1.Socket网络通信模式:

常见的Socket网络通信模式的类型有两种:流式Socket和数据报式Socket

1.流式Socket(SOCK_STREAM)是一种面向连接的Socket,其提供了双向、有序的、无重复的、以及无记录边界的数据流服务,适合处理大量数据.所以它是针对于面向连接的TCP服务应用的

2.数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,提供支持双向的数据流,它不保证传输数据的准确性,但保留了记录边界。它是针对于面向无连接的UDP服务应用的

3.原始socket(SOCK_RAW),原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。

 

2.TCP网络通信过程

学习网站: https://blog.csdn.net/xiaoquantouer/article/details/58001960?utm_source=blogxgwz5

服务端执行流程如下:

1、加载套接字库,创建套接字(WSAStartup()/socket());

2、绑定套接字到一个IP地址和一个端口上(bind());

3、将套接字设置为监听模式等待连接请求(listen());

4、请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());

5、用返回的套接字和客户端进行通信(send()/recv());

6、返回,等待另一个连接请求;

7、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());

客户端执行流程如下:

1、加载套接字库,创建套接字(WSAStartup()/socket());

2、向服务器发出连接请求(connect());

3、和服务器进行通信(send()/recv());

4、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());

 

3.UDP网络通信过程

服务端执行流程如下:

1、加载套接字库,创建套接字(WSAStartup()/socket());

2、绑定套接字到一个IP地址和一个端口上(bind());

5、和客户端进行通信(send()/recv());

6、返回,等待另一个连接请求;

7、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());

客户端执行流程如下:

1、加载套接字库,创建套接字(WSAStartup()/socket());

2、设置对方的IP地址和端口等属性;

3、和服务器进行通信(send()/recv());

4、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());

 

4.Socket的函数:

1.初始化和释放套接字库

WSAStartup()初始化套接字库

WSAStartup()的函数原型如下:

int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);

函数调用成功将返回0,否则,调用函数失败。

参数:

wVersionRequested 表示当前套接字的版本号     WORD wVersionRequested=MAKEWORD(2,2);

lpWSAData指向结构体 WSADATA的指针变量,表示获取到的套接字库详细信息。

当程序退出时,用户应该调用WSACleanup()释放该套接字库

2.建立Socket

在程序中如果要使用Socket,必须先建立一个Socket

套接字是通信端点的抽象,实现端对端之间的通信。

/* 套接字 */  
/* 
 * 函数功能:创建套接字描述符; 
 * 返回值:若成功则返回套接字非负描述符,若出错返回-1; 
 * 返回值:函数执行成功将返回新创建的套接字句柄,否则,将返回INVALID_SOCKET表示失败
 * 函数原型: 
 */  
#include<sys/types.h> //提供各种数据类型的定义
#include <sys/socket.h> //提供socket函数及其数据结构的定义

int socket(int family, int type, int protocol);  
/* 
 * 说明: 
 * socket类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符; 
 * family 表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下: 
 * (1)AF_INET         IPv4因特网域 
 * (2)AF_INET6        IPv6因特网域 
 * (3)AF_UNIX         Unix域 
 * (4)AF_ROUTE        路由套接字 
 * (5)AF_KEY          密钥套接字 
 * (6)AF_UNSPEC       未指定 
 * 
 * type确定socket的类型,常用类型如下: 
 * (1)SOCK_STREAM     有序、可靠、双向的面向连接字节流套接字(TCP)
 * (2)SOCK_DGRAM      长度固定的、无连接的不可靠数据报套接字 (UDP)
 * (3)SOCK_RAW        原始套接字 
 * (4)SOCK_SEQPACKET  长度固定、有序、可靠的面向连接的有序分组套接字 
 * 
 * protocol指定协议,常用取值如下: 
 * (1)0               选择type类型对应的默认协议 
 * (2)IPPROTO_TCP     TCP传输协议 
 * (3)IPPROTO_UDP     UDP传输协议 
 * (4)IPPROTO_SCTP    SCTP传输协议 
 * (5)IPPROTO_TIPC    TIPC传输协议 
 * 
 */   

 

Socket套接字其实是一个指向内部数据结构的指针,其指向套接字表入口。调用Socket函数时,Socket执行体将建立一个Socket,实际上"建立一个Socket"意味着为这个Socket数据结构分配存储空间

两个网络程序之间的一个网络连接包括五种信息: 通信协议、本地协议地址,本地主机端口、远程主机端口和远端协议地址

 

3.绑定Socket

1.bind函数 

通过Socket调用返回一个Socket套接字后,在使用Socket进行网络传输以前,必须配置这个Socket。

面向连接的Socket客户端通过调用connect()函数在Socket数据结构中保存本地和远端信息

无连接Socket的客户端和服务端,以及面向连接Socket的服务端通过调用bind函数来配置本地信息

bind()函数将Socket与本机上的一个端口相关联,随后就可以在该端口上监听服务端请求

/* 套接字的基本操作 */  

/* 
 * 函数功能:将协议地址绑定到一个套接字;其中协议地址包含IP地址和端口号; 
 * 返回值:若成功则返回0,若出错则返回-1; 
 * 函数原型: 
 */  
#include<sys/types.h>
#include <sys/socket.h>  
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);  
/* 
 * 说明: 
 * sockfd 为套接字描述符; 
 * addr是一个指向特定协议地址结构的指针,协议地址结构包含IP地址和端口号; 
 * addrlen是地址结构的长度,常被设置为sizeof(struct sockaddr); 
 */  

 

注:1.bind()函数成功被调用时返回0;出现错误时返回-1并设置相应的错误号

2.在调用bind()函数时一般不要将端口号设置为小于1024的值,因为1-1024都是保留端口号

 

 2.sockaddr结构

 struct sockaddr结构类型是用来保存Socket信息的

struct sockaddr{
    unsigned short sa_family; //地址族,AF_xxx 
    char sa_data[14]; //14字节的协议地址
};

属性值:

sa_ family一般为AF_ INET代表TCP/IP地址;

sa_ data则包含该Socket的IP地址和端口号

 

3.sockaddrin结构 

sockaddrin结构是用来专门标识TCP/IP协议下的地址结构

struct  sockaddr_in{
    short int sin_family; //地址族
    unsigned short int sin_port; //端口号
    struct in_addr sin_addr; //IP地址
    unsigned char sin_zero[8];//填充0,以保持与struct sockaddr 同样大小
}

属性值:

其中sin_ family必须设置为AF_ INET;

sin_ port为服务端口(注意不要使用系统已经固定的特殊端口 eg:HTTP服务的80端口)

sin_ addr 是一个unsigned long 的IP地址;

sin_ zero用来将sockaddr_in结构填充到与struct sockaddr同样的长度,可以用memset将其置0

 

注:sin_ addr结构体中只有一个唯一的字段s_ addr,表示IP地址,该字段是一个整数,一般用函数inet_ addr()把字符串形式的IP地址转换成unsigned long型的整数值后再置给s_ addr。

4.in_addr结构

struct in_addr{
    union{
        struct{
            unsigned char s_b1,s_b2,s_b3,s_b4; 
        }S_un_b; //用4个u_char字符描述IP地址

        struct{
            unsigned short s_w1,s_w2;
        }S_un_w; //用两个u_short类型描述IP地址
        unsigned long S_addr; //用一个u_long类型描述IP地址
    }S_un;
};

 

注:in_addr中所描述的IP地址均为网络字节顺序

5.指向sockaddr_in的指针和指向sockaddr的指针可以相互转换

6.使用bind()函数时,可以用下面的赋值实现自动获得本地IP地址和随机获取一个没有被占有的端口号

MyAddr.sin_port=0;//系统随机选择一个未被使用的端口号
MyAddr.sin_addr.s_addr=INADDR_ANY; //填入本地IP地址

 

7.字节顺序转换函数

注意:在使用bind()函数时需要将sin_port和s_addr转换成为网络字节优先顺序。

1.计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。Internet上数据以高位字节顺序优先在网络上传输,所以对于在内部以低位字节优先方式存储数据的机器,在Internet上传输数据时就需要进行转换,否则就会出现数据不一致的情况。

2.从数据存储的角度来讲,网络字节顺序是将数据中最重要的字节首先进行存储,而主机字节顺序则将不重要的字节首先存储

在这里介绍几个网络字节顺序与主机字节顺序之间的转换函数

1.htonl() 将32位值从主机字节序转换成网络字节序(host to network l表示 long)

2.htons() 将16位值从主机字节序转换成网络字节序(host to network s表示 short)

3.ntohl() 将32位值从网络字节序转换成主机字节序(network to host l表示 long)

4.htons() 将16位值从网络字节序转换成主机字节序(network to host s表示 short)

5.inet_addr 将字符串IP地址转换为以网络字节顺序排列的IP地址

函数原型:unsigned long inet_addr(const char * str);

6.inet_nota 将一个以网络字节顺序排列的IP地址转换为字符串IP

函数原型:char * inetntoa(struct inaddr addr);

4.客户端连接建立

面向连接的客户端程序可以使用Connect函数来配置Socket,并与远端服务器建立一个TCP连接

/* 
 * 函数功能:建立连接,即客户端使用该函数来建立与服务器的连接; 
 * 返回值:若成功则返回0,出错则返回-1; 
 * 函数原型: 
 */  
#include <sys/socket.h>  
#include<sys/types.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);  
/* 
 * 说明: 
 * sockfd是系统调用的套接字描述符,即由socket函数返回的套接字描述符; 
 * servaddr是目的套接字的地址,该套接字地址结构必须包含目的IP地址和目的端口号,即想与之通信的服务器地址; 
 * addrlen是目的套接字地址的大小; 
 * 
 * 如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址,即内核会确定源IP地址,并选择一个临时端口号作为源端口号; 
 */  

 

注:

1.在进行客户端程序时,无需调用bind()函数,因为在这种情况下 只需知道目的机器的IP地址,而客户端程序通过那个端口与服务端建立连接调用者并不需要关心,connect函数会为程序自动选择一个未被占用的端口,并通知调用者数据什么时候会到达这个端口。

2.connect()函数启动和远端主机的直接连接。只有面向连接的客户程序使用Socket时才需要将此Socket与远端主机相连,无连接协议从不建立 直接连接。面向连接的服务器也从不启动连接,它只是被动地在协议端口监听客户的请求。

3.TCP 套接字调用connect 函数将建立 TCP 连接(执行三次握手,而且仅在连接建立成功或出错时才返回

5.服务器端的监听

服务器端程序调用listen()函数使Socket处于被动的监听模式,并为该Socket建立一个输入数据队列,将到达的服务请求保存在此队列中, 直到程序处理。

/* 
 * 函数功能:接收连接请求; 
 * 函数原型: 
 */  
#include <sys/socket.h>  
#include<sys/types.h>
int listen(int sockfd, int backlog);//若成功则返回0,若出错则返回-1;  
/* 
 * sockfd是套接字描述符; 
 * backlog是该进程所要入队请求的最大请求数量,进入的连接请求将在队列中等待accept。backlog对队列中等待服务请求的数目进行了限制,大多数系统默认值为20.如果一个服务请求到来时,输入队列已满,该Socket将拒绝服务连接请求,客户端收到一个出错信息; 
 */  

 

listen 函数一般应该在调用socket 和bind 这两个函数之后,并在调用accept 函数之前调用。 内核为任何一个给定监听套接字维护两个队列:

1.未完成连接队列,每个这样的 SYN 报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手过程。这些套接字处于 SYN_REVD 状态;

2.已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项。这些套接字处于 ESTABLISHED 状态;

6.服务器端的接收

当建立好输入队列后,服务器将调用accept()函数,然后睡眠并等待客户的连接请求。当有客户连接时,服务器端程序通过调用accept()函数让服务器接受客户的连接请求。

//accept()函数的原型如下: 
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,sturct sockaddr *addr,socklen_t *addrlen);
/*
sockfd:是系统函数socket()返回的套接字描述符
addr:是一个回传的指向数据结构sockaddr的指针,accept()函数将从客户的连接请求之中自动抽取远端客户主机的端点地址信息并存入到该指针所指向的数据结构之中。
addrlen:远端地址结构addr的长度,可以用sizeof(struct sockaddr)来获取。
*/

 

accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。

此时我们需要区分两种套接字,一种套接字正如accept的参数sockfd,它是监听套接字,在调用listen函数之后,一个套接字会从主动连接的套接字变身为一个监听套接字;而accept返回是一个连接套接字,它代表着一个网络已经存在的点点连接。

如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。收到服务请求的监听套接字仍可以继续在以前的Socket上监听。

7.面向连接的数据发送

在Socket中,可以利用send()函数用于面向连接的Socket上进行数据发送

send()函数的原型如下: int send(int sockfd,const void *msg,int len,int flags);

其中,sockfd是用来传输数据的Socket套接字;msg是一个指向要发送数据的指针;len是以字节为单位的数据的长度;flags一般情况下置为0。

send()函数调用后返回实际上发送出的字节数,可能会少于需要发送的数据。在程序中应该将send()的返回值与将要发送的字节数进行比较。

当send()返回值与len不匹配时,应该对这种情况进行处理,如下:

char *msg = "Hello World!"; 
int nlen=strlen(msg); //数据的长度
int nslen=0;//用于保存已经发送的长度
while(nslen<nlen){
    nslen+=send(sock,msg+nslen,(nlen-nslen),0);
}

 

8.面向连接的数据接收

在Socket中,可以利用recv()函数用于面向连接的Socket上进行数据接收

其函数原型如下:int recv(int sockfd, void *buf, int len,unsigned int flags);

其中,sockfd是接收数据的套接字;buf是存放接收数据的缓冲区;len是缓冲区的长度;flags被置为0。

recv()返回实际接收的字节数。

当发生错误时,返回-1并设置相应的错误值。

9.无连接的数据发送

sendto()函数用于无连接的数据报Socket方式下进行数据发送。由于本地Socket并没有与远端的主机建立连接,所以在发送数据时应指明目的地址。

sendto()的函数原型如下: int sendto(int sockfd,const void *msg,int len,unsigned int flags,const struct sockaddr *to,int tolen);

这个函数比send()多了两个参数,其中to表示目的主机的IP地址和端口号信息,而tolen常常被设置为sizeof(struct sockaddr)。

sendto返回实际发送的数据字节长度。在出现错误时返回-1并设置相应的错误值。

10.无连接的数据接收

recvfrom()函数用于在无连接的数据报Socket方式下进行数据接收。

recvfrom()函数原型如下: int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockeaddr *from,int fromlen);

其中,from是一个struct sockaddr类型的变量,该变量保存源机的IP地址和端口号。 fromlen一般设置为sizeof(struct sockaddr)。

recvfrom返回实际接收的数据字节长度,在出现错误时返回-1并设置相应的错误值。

注:如果对数据报类型的Socket调用connect()函数时,其实也可以利用send()和recv()进行数据传输,但该Socket仍为数据报类型Socket,并且只会利用传输层的UDP服务,但在发送和接收数据报时,内核会自动为之加上目的地和源地址信息。

11.域名转换为IP地址

由于IP地址难以记忆和读写,所以为了方便,人们常常用域名来表示主机,这就需要进行域名和IP地址的转换。函数gethostbyname()可以完成 域名转换。

函数原型:struct hostent *gethostbyname(const char *name);

函数返回一种名为hostent的结构类型,它的定义如下:

struct hostent 
{ 
     char *h_name;        /* 主机的官方域名 */ 
     char **h_aliases;    /* 一个以NULL结尾的主机别名数组 */ 
     int    h_addrtype;   /* 返回的地址类型,在Internet环境下为AF-INET */ 
     int h_length;        /*地址的字节长度 */ 
     char **h_addr_list;  /* 一个以0结尾的数组,包含该主机的所有地址*/ 
}; 
#define h_addr h_addr_list[0] /*在h-addr-list中的第一个地址*/

 

当gethostbyname()函数调用成功,返回指向struct hostent的指针,当调用失败时返回-1。

12.closesocket()函数

系统函数closesocket()用于在服务器和客服端之间的所有数据收发操作结束以后关闭套接字、以释放该套接字所占用的资源。close()函数若调用成功将返回0,若出错则返回-1。

该函数原型如下: int closesocket(sockfd);

注:系统中的每一个文件或套接字都有一个引用计数,表示当前打开或者正在引用该文件或套接字的进程的个数。调用close()函数终止一个连接时,它只是减少了描述符的引用计数,并不直接关闭对应的连接,只是当描述符的引用计数为0时,才真正关闭该连接。因此,在只有一个进程使用某个套接字时,close()函数会将该套接字的读写都关闭,这就容易导致发起关闭的一方还没有读完所有数据就关闭连接了,从而使得对方发来的数据将会被丢弃;但如果有多个进程共享某个套接字时,则系统函数close()每调用一次,都只是会使得其引用计数减一,而只有等到其引用计数为0时,也就是说,只有等到所有进程都调用了close()函数来关闭该套接字时,该套接字才会被最终释放;为此,在有多个进程共享某个套接字的时候,当某个进程调用close()函数关闭了一个套接字之后,除了使得该进程将不能再访问该套接字之外,其他正在引用该套接字的进程均可继续正常使用该套接字与远端机器进行通信。

13.shutdown()函数

系统函数shutdown()用于在套接字的某一个方向上关闭数据传输,而另一个方向上的数据传输仍可继续进行。shutdown()函数若调用成功将返回0,若出错则返回-1。

函数原型如下: int shutdown(int sockfd,int how);

 

参数how允许为shutdown操作选择

0:仅关闭读,套接字将不再接受任何数据,且将套接字接收到的缓冲区中的现有数据全部丢弃。

1:仅关闭写,当前留在缓冲区中的数据将被发送完,进程将不能够再对该套接字调用任何写函数。

2:同时关闭读和写,与close()函数功能类似,但不同的是,shutdown()函数在关闭描述符时不考虑该套接字描述符的引用计数,而是直接关闭该套接字。

注:与close()函数不同,在有多个进程共享某个套接字时,如果一个进程调用了shutdown()函数关闭某个套接字后,将会使得其他的所有进程都无法再使用该套接字进行通信。

基于TCP的Socket编程

1.服务端代码

#include<cstdio>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib");
#define PORT 8888
#define BACKLOG 20 
int main(){
    WORD sockVersion =MAKEWORD(2,2);
    WSADATA wsaData;
    if(WSAStartup(sockVersion,&wsaData)!=0){
        printf("初始化套接字库失败\n");
        return 0;
    }
    int slisten; 
    if((slisten=socket(AF_INET,SOCK_STREAM,0))==-1){
        printf("socket error!\n");
        return 0;
    }
    sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(PORT);
    addr.sin_addr.s_addr=INADDR_ANY;
    printf("本地ip地址:%s\n",inet_ntoa(addr.sin_addr));
    if(bind(slisten,(sockaddr *)&addr,sizeof(addr))==-1){
        printf("bind error\n");
        closesocket(slisten);
        return 0;
    }
    if(listen(slisten,BACKLOG)==-1){
        printf("listen error\n");
        closesocket(slisten);
        return 0;
    }
    int sClient;
    sockaddr_in remoteAddr;
    int sin_size=sizeof(struct sockaddr_in);
    char revData[255];
    while(true){
        printf("等待连接...\n");
        if((sClient=accept(slisten,(struct sockaddr *)&remoteAddr,&sin_size))==-1){
            printf("accept error!\n");
            closesocket(slisten);
            continue;
        }
        printf("接受到一个连接:%s\n",inet_ntoa(remoteAddr.sin_addr));
        //接受数据 
        int ret=recv(sClient,revData,255,0);
        if(ret>0){
            revData[ret]='\0';
            printf("%s\n",revData);
        }
        //const char *sendData ="你好,TCP客户端!\n"; 
        char sendData[255];
        scanf("%s",sendData);
        send(sClient,sendData,strlen(sendData),0);
        closesocket(sClient);
    }
    closesocket(slisten);
    WSACleanup();
    return 0;
}

 

2.客户端代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<winsock2.h> 

#pragma comment(lib,"ws2_32.lib")
#define PORT 8888
using namespace std;
int main(){
    WORD sockVersion = MAKEWORD(2, 2);
    WSADATA data;
    if(WSAStartup(sockVersion, &data)!=0){
        printf("初始化套接字库失败\n");
        return 0;
    }

    int sClient;
    if((sClient=socket(AF_INET, SOCK_STREAM,0))==-1){
        printf("socket error!");
            return 0;
    }
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr= inet_addr("127.0.0.1");
    if(connect(sClient,(sockaddr *)&addr,sizeof(addr))==-1){
        printf("connect error !");
        closesocket(sClient);
        return 0;
    }   
    //发送数据 
    const char * sendData;
    scanf("%s",sendData);

    send(sClient, sendData, strlen(sendData), 0);

    //接收数据 
    char recData[255];
    int ret = recv(sClient, recData, 255, 0);
    if(ret>0){
        recData[ret]='\0';
        printf("%s\n",recData);
    } 
    closesocket(sClient);

    WSACleanup();
    return 0;
}

 

基于UDP的Socket编程

1.服务端代码

#include<winsock2.h>
#include<cstdio>
#pragma comment(lib,"WS2_32.lib")
#define PORT 8888
#define BACKLOG 20

int main() {
    WSADATA data;
    WORD w=MAKEWORD(2,2);
    if(WSAStartup(w,&data)!=0){
        printf("初始化套接字库失败\n");
        return 0;
    }
    int slisten;
    if((slisten=socket(AF_INET,SOCK_DGRAM,0))==INVALID_SOCKET){
        printf("socket error\n");
        return 0;
    }
    sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(PORT);
    addr.sin_addr.S_un.S_addr=INADDR_ANY;
    printf("本地ip地址:%s\n",inet_ntoa(addr.sin_addr));
    if(bind(slisten,(sockaddr *)&addr,sizeof(addr))==-1){
        printf("bind error\n");
        closesocket(slisten);
        return 0;
    } 
    char recvData[255];
    char sendData[255];
    sockaddr_in remoteAddr;
    int n=sizeof(remoteAddr);
    while(1){
        printf("等待连接...\n"); 
        int ret=recvfrom(slisten,recvData,255,0,(sockaddr *)&remoteAddr,&n);
        printf("接受到一个连接:%s\n",inet_ntoa(remoteAddr.sin_addr));
        if(ret>0){
            recvData[ret]='\0';
            printf("%s\n",recvData);
        }
        scanf("%s",sendData);
        sendto(slisten,sendData,sizeof(sendData),0,(sockaddr *)&remoteAddr,n);
    }
    closesocket(slisten);
    WSACleanup();
}

 

2.客户端代码

#include<winsock2.h>
#include<cstdio>

#pragma comment(lib,"WS2_32.lib")
#define PORT 8888

int main(){
    WSADATA data;
    WORD w=MAKEWORD(2,2);
    if(WSAStartup(w,&data)!=0){
        printf("初始化套接字库失败\n");
        return 0;
    }
    int sClient;
    if((sClient=socket(AF_INET,SOCK_DGRAM,0))==INVALID_SOCKET){
        printf("socket error\n");
        return 0;
    }
    sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(PORT);
    addr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
    char recvData[255],sendData[255];
    sockaddr_in remoteAddr;
    int n=sizeof(remoteAddr);
    scanf("%s",sendData);
    sendto(sClient,sendData,sizeof(sendData),0,(sockaddr *)&addr,sizeof(addr)); 
    recvfrom(sClient,recvData,255,0,(sockaddr *)&remoteAddr,&n);
    printf("%s\n",recvData);
    closesocket(sClient);
    WSACleanup();
}

 

2018-11-01

推荐阅读