client端:(基于TCP)
需要做的事情:
- 创建socket插口,该插口OS自动分配port。不用init
- 直接和服务器端的socket进行connect,需要服务器端的ip+port
- 准备好要发送给服务器端的信息(本实验用的是字符串)send函数
- 服务器响应回来,用recv函数接受信息。放入某个容器(数组, 甚至是文件)
- 关闭插口,终止和server端的通信。
#include <WINSOCK2.H>
#include <STDIO.H>
#define MAX_MSG_SIZE 1024
#define SERVER_PORT 49152
#pragma comment(lib,"ws2_32.lib") //表示链接Ws2_32.lib这个库。
int main(int argc, char* argv[])
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0)
{
return 0;
}//上面这几句必不可少,否则无法创建socket插口,即socket()会报错。
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//客户端的插口
if (sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(SERVER_PORT);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//这就是要准备好server端的信息,便于插口去连接。
if (connect(sclient, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return 0;
}
const char* sendData = "hi,I am client!\n";//要发送的信息
send(sclient, sendData, strlen(sendData), 0);
char recData[255];
int ret = recv(sclient, recData, 255, 0);//返回的是实际copy字节数
if (ret > 0)
{
printf(recData);
}
closesocket(sclient);
WSACleanup();//终止对套接字库的使用。
return 0;
}
server端(基于TCP)
- server端需要选创建一个socket
- 然后把server端需要提供服务的那个进程和这个插口绑定一起。
- listen监听,阻塞到有connect到来
- accept接收连接
- 进行recv和send操作,交互数据
- 循环上述过程,持续监听,可以建立新的连接,然后重复
常用的 Socket 类型有两种:流
式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是
一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket
是一种无连接的 Socket,对应于无连接的 UDP 服务应用。
#include<stdio.h>
#include <winsock2.h>
#define MAX_MSG_SIZE 1024
#define SERVER_PORT 49152
#define BACKLOG 5
#pragma comment(lib, "Ws2_32.lib")
int main()
{
WORD version(0);
WSADATA wsadata;
int socket_return(0);
version = MAKEWORD(2, 0);
socket_return = WSAStartup(version, &wsadata);//开始对套接字库的使用。
if (socket_return != 0)
{
return 0;
}//这些不能少,如果少的话,就无法正确创建socket。
int ser_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);/*创建连接的SOCKET */
struct sockaddr_in ser_addr; /* server地址信息 */
struct sockaddr_in cli_addr; /* 客户端地址信息 */
char msg[MAX_MSG_SIZE];/* 缓冲区,存放从客户端来的请求*/
memset(msg, 0, MAX_MSG_SIZE);//清空
if (ser_sockfd < 0)
{/*创建失败 */
fprintf(stderr, "socker Error:%s\n", strerror(errno));
exit(1);
}
/* 初始化服务器地址*/
int addrlen = sizeof(struct sockaddr_in);
memset(&ser_addr, 0, addrlen);
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 就是本机的所有ip
//如果要手动指定ip可以这么使用
// my_addr.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx"); inet_addr用于把32位的ip地址转为unsigned long
ser_addr.sin_port = htons(SERVER_PORT);//为这个server进程指定端口号
if (bind(ser_sockfd, (struct sockaddr*)&ser_addr, sizeof(struct sockaddr_in))
< 0)
{ /*绑定失败 */
fprintf(stderr, "Bind Error:%s\n", strerror(errno));
exit(1);
}
/*侦听客户端请求*/
if (listen(ser_sockfd, BACKLOG) < 0)//BACKLOG的作用就是设置accpet接收请求队列的大小
{
fprintf(stderr, "Listen Error:%s\n", strerror(errno));
closesocket(ser_sockfd);
exit(1);
}
while (1)
{/* 等待接收客户连接请求*/
int cli_sockfd = accept(ser_sockfd, (struct sockaddr*)&cli_addr,
&addrlen);//传入绑定好的进程插口,和客户端地址(ip+port,给出的是cli_addr,以及它的长度)实现通信
// printf("%d",cli_sockfd);
if (cli_sockfd <= 0)
{
fprintf(stderr, "Accept Error:%s\n", strerror(errno));
}
else
{/*开始服务*/
/* 接受数据*/
recv(cli_sockfd, msg, MAX_MSG_SIZE, 0);
printf("received a connection from %s\n", inet_ntoa(cli_addr.sin_addr));
printf("%s\n", msg);/*在屏幕上打印出来 */
//strcpy(msg, "hi,I am server!");
/*发送的数据*/
send(cli_sockfd, "hi,I am server!", sizeof(msg), 0);
closesocket(cli_sockfd);
}
}//server是infinity工作,所以是while(1)
closesocket(ser_sockfd);
}
基于UDP
server端
没有listen’和accept,直接recvfrom和sendto
首先调用 socket 函数创建一个 Socket,然后调用 bind
函数将其与本机地址以及一个本地端口号绑定,接收到一个客户端时,服务器
显示该客户端的 IP 地址,并将字串返回给客户端。
#include <WINSOCK2.H>
#include <STDIO.H>
#define MAX_MSG_SIZE 1024
#define SERVER_PORT 49152
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char** argv)
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
int ser_sockfd;
int len;
//int addrlen;
int addrlen;
char seraddr[100];
struct sockaddr_in ser_addr;
/*建立socket*/
ser_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (ser_sockfd < 0)
{
printf("I cannot socket success\n");
return 1;
}
/*填写sockaddr_in 结构*/
addrlen = sizeof(struct sockaddr_in);
memset(&ser_addr, 0, addrlen);
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ser_addr.sin_port = htons(SERVER_PORT);
/*绑定客户端*/
if (bind(ser_sockfd, (struct sockaddr*)&ser_addr, addrlen) < 0)
{
printf("connect");
return 1;
}
while (1)
{
memset(&seraddr, 0, sizeof(seraddr));
len = recvfrom(ser_sockfd, seraddr, sizeof(seraddr), 0, (struct
sockaddr*)&ser_addr, &addrlen);
/*显示client端的网络地址*/
printf("receive from %s\n", inet_ntoa(ser_addr.sin_addr));
/*显示客户端发来的字串*/
printf("recevce:%s", seraddr);
/*将字串返回给client端*/
sendto(ser_sockfd, seraddr, len, 0, (struct sockaddr*)&ser_addr, addrlen);
}
}
client端
首先调用 socket 函数创建一个 Socket,填写服务器地
址及端口号,从标准输入设备中取得字符串,将字符串传送给服务器端,并接
收服务器端返回的字符串。最后关闭该 socket。
没有connect,创建好直接sendto和recvfrom来实现发送接收的通信即可
#include <WINSOCK2.H>
#include <STDIO.H>
#define MAX_MSG_SIZE 1024
#define SERVER_PORT 49152
#pragma comment(lib,"ws2_32.lib")
int GetServerAddr(char* addrname)
{
printf("please input server addr:");
scanf("%s", addrname);
return 1;
}
int main(int argc, char** argv)
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
int cli_sockfd;
int len;
char seraddr[14];
struct sockaddr_in cli_addr;
char buffer[256];
GetServerAddr(seraddr);
/* 建立socket*/
cli_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (cli_sockfd < 0)
{
printf("I cannot socket success\n");
return 1;
}
/* 填写sockaddr_in*/
int addrlen = sizeof(struct sockaddr_in);
memset(&cli_addr, 0, addrlen);
cli_addr.sin_family = AF_INET;
cli_addr.sin_addr.s_addr = inet_addr(seraddr);
//cli_addr.sin_addr.s_addr=htonl(INADDR_ANY);
cli_addr.sin_port = htons(SERVER_PORT);
memset(buffer, 0, sizeof(buffer));
/* 从标准输入设备取得字符串*/
scanf("%s", buffer);
len = strlen(buffer);
/* 将字符串传送给server端*/
sendto(cli_sockfd, buffer, len, 0, (struct sockaddr*)&cli_addr, addrlen);
/* 接收server端返回的字符串*/
len = recvfrom(cli_sockfd, buffer, sizeof(buffer), 0, (struct
sockaddr*)&cli_addr, &addrlen);
//printf("receive from %s\n",inet_ntoa(cli_addr.sin_addr));
printf("receive: %s", buffer);
closesocket(cli_sockfd);
}
常用的 Socket 类型有两种:流
式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是
一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket
是一种无连接的 Socket,对应于无连接的 UDP 服务应用。
TCP
优点:
1.TCP 提供以认可的方式显式地创建和终止连接。
2.TCP 保证可靠的、顺序的(数据包以发送的顺序接收)以及不会重复
的数据传输。
3.TCP 处理流控制
4.允许数据优先
5.如果数据没有传送到,则 TCP 套接口返回一个出错状态条件。
6.TCP 通过保持连续并将数据块分成更小的分片来处理大数据块。—无
需程序员知道
缺点: TCP 在转移数据时必须创建(并保持)一个连接。这个连接给通
信进程增加了开销,让它比 UDP 速度要慢。
UDP 优缺点:
1.UDP 不要求保持一个连接
2.UDP 没有因接收方认可收到数据包(或者当数据包没有正确抵达而自
动重传)而带来的开销。
3.设计 UDP 的目的是用于短应用和控制消息
4.在一个数据包连接一个数据包的基础上,UDP 要求的网络带宽比 TCP
更小。
对于UDP传输需要指定超时了解客户端断开连接
使用setsockopt函数来实现超时判断。
注意,这个函数是全局作用的,所以放在开头指定这个端口在recv的时候一旦过了指定的time就默认连接断开即可。
int nNetTimeout = 3000; //3000ms超时
if (SOCKET_ERROR == setsockopt(ser_sockfd, SOL_SOCKET, SO_RCVTIMEO, (char*)&nNetTimeout, sizeof(int)))
{
printf("Set Ser_RecTIMEO error !\r\n");
}
第一个参数指定了作用的socket是哪一个。SOL_SOCKET就是通用的socket选项。SO_RCVTIMEO指定了是接收侦听的时候判断超时。nNetTimeout设定了要判断超时的时间有多久。最后一个是对这个时间(int)的大小传入。
这样在后续recv接收返回值的时候,就可以对ret和0比较。小等于0,就是断开连接。
代码如下: