【转/TCP协议编程】 基于TCP的Socket 编程
基于TCP(面向连接)的socket编程,分为客户端和服务器端。
客户端的流程如下:
(1)创建套接字(socket)
(2)向服务器发出连接请求(connect)
(3)和服务器端进行通信(send/recv)
(4)关闭套接字
服务器端的流程如下:
(1)创建套接字(socket)
(2)将套接字绑定到一个本地地址和端口上(bind)
(3)将套接字设为监听模式,准备接收客户端请求(listen)
(4)等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)
(5)用返回的套接字和客户端进行通信(send/recv)
(6)返回,等待另一个客户请求。
(7)关闭套接字。
下面通过一个具体例子讲解一下具体的过程和相关的函数。
客户端代码,运行于vs2008
// ClientTest.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> #define SERVER_PORT 5208 //侦听端口 int _tmain(int argc, _TCHAR* argv[])
{
WORD wVersionRequested;
WSADATA wsaData;
int ret;
SOCKET sClient; //连接套接字
struct sockaddr_in saServer; //服务器地址信息
char *ptr;
BOOL fSuccess = TRUE; //WinSock初始化
wVersionRequested = MAKEWORD(, ); //希望使用的WinSock DLL的版本
ret = WSAStartup(wVersionRequested, &wsaData); //加载套接字库
if(ret!=)
{
printf("WSAStartup() failed!\n");
//return 0;
}
//确认WinSock DLL支持版本2.2
if(LOBYTE(wsaData.wVersion)!= || HIBYTE(wsaData.wVersion)!=)
{
WSACleanup(); //释放为该程序分配的资源,终止对winsock动态库的使用
printf("Invalid WinSock version!\n");
//return 0;
} //创建Socket,使用TCP协议
sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sClient == INVALID_SOCKET)
{
WSACleanup();
printf("socket() failed!\n");
//return 0;
} //构建服务器地址信息
saServer.sin_family = AF_INET; //地址家族
saServer.sin_port = htons(SERVER_PORT); //注意转化为网络节序
saServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //连接服务器
ret = connect(sClient, (struct sockaddr *)&saServer, sizeof(saServer));
if (ret == SOCKET_ERROR)
{
printf("connect() failed!\n");
closesocket(sClient); //关闭套接字
WSACleanup();
//return 0;
} char sendMessage[]="ZhongXingPengYue";
ret = send (sClient, (char *)&sendMessage, sizeof(sendMessage), );
if (ret == SOCKET_ERROR)
{
printf("send() failed!\n");
}
else
printf("client info has been sent!");
char recvBuf[];
recv(sClient,recvBuf,,);
printf("%s\n",recvBuf);
closesocket(sClient); //关闭套接字
WSACleanup();
getchar();
//return 0;
} <span style="font-size:16px;"></span>
<span style="font-size:16px;"></span>
第一步,加载套接字。使用WSAStartup 函数,如:ret = WSAStartup(wVersionRequested, &wsaData)。WSAStartup函数的原型为
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData)
第一参数wVersionRequested,用来指定准备加载的winsock库的版本。利用MAKEWORD(x,y)宏来赋值。x是高位字节,表示副版本号;y是低位字节,表示主版本号。MAKEWORD(2, 2)表示版本号为2.2。
第二个参数是指向WSADATA结构的指针,是一个返回值,保存了库版本的有关信息。
第二步,创建套接字。使用socket函数,如:sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)。socket函数的原型为:
SOCKET socket(int af, int type, int protocol );
第一个参数,指定地址族,对于TCP/IP协议的套接字,只能为AF_INET;
第二个参数,指定socket类型,SOCK_STREAM指产生流式套接字,SOCK_DGRAM指产生数据报套接字,TCP/IP协议使用SOCK_STREAM。
第三个参数,与特定的地址家族相关的协议,TCP协议一般为IPPROTO_TCP。也可以写0,那么系统会根据地址格式和套接字类别,自动选择一个适合的协议。
如果socket创建成功,则返回一个新的SOCKET数据类型的套接字描述符;若失败,则返回INVALID_SOCKET,由此可以判断是否创建成功。
第三步,连接服务器。使用connect函数,如:ret = connect(sClient, (struct sockaddr *)&saServer, sizeof(saServer))。connect函数函数原型为
int connect(SOCKET s, const struct sockaddr FAR* name, int namelen);
第一个参数是将在上面建立连接的那个套接字的描述符,即之前创建socket的返回值sClient。
第二个参数是要连接的服务器端的地址信息。它是一个结构体类型struct sockaddr_in ,需要在调用connect函数之前构建服务器地址信息。sockaddr_in的定义如下:
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[]
};
设置服务器端口时,用到htons函数,该函数把一个u_short类型的值从主机字节顺序转换为TCP/IP网络字节顺序,因为不同的计算机存放多字节的顺序不同(基于Intel CPU是高字节存放在低地址,低字节存放在高地址),所以网络中不同主机间通信时,要统一采用网络字节顺序。设置服务器IP地址时,使用到inet_addr函数,它是将点分十进制的IP地址的字符串转换成unsigned long型。inet_ntoa函数做相反的转换。
第三个参数是服务器端地址结构体的大小。
第四步,发送。使用send函数向服务器发送数据,如:ret = send (sClient, (char *)&sendMessage, sizeof(sendMessage), 0)。send函数的原型为
int send(SOCKET s, const char FAR* buf, int len, int flags);
第一个参数,是一个与服务器已经建立连接的套接字。
第二个参数,指向包含要发送的数据的缓冲区的指针。
第三个参数,是所指向的缓冲区的长度。准确的说,应该是所要发送的数据的长度,因为不是缓冲区的所有数据都要同时发送。
第四个参数,它设定的值将影响函数的行为,一般将其设置为0即可。
如果发送失败,send会返回SOCKET_ERROR,由此可以判断发送是否成功。
第五步,接收。使用recv函数接收服务器发过来的数据,如recv(sClient,recvBuf,100,0)。recv函数的原型为
int recv(SOCKET s, const char FAR* buf, int len, int flags);
recv函数的参数的含义和send函数参数含义差不多,只是第二个参数是指向用来保存接收数据的缓冲区的指针。recv函数的返回值应该是所接收的数据的长度,如果返回SOCKET_ERROR表示接收失败;返回0表示服务器端关闭连接。
第六步,关闭socket,释放资源。使用closesocket函数关闭套接字,如closesocket(sClient);使用WSACleanup函数释放为该程序分配的资源,终止对winsock动态库的使用,如WSACleanup();
服务器端代码,运行于vs2008
// ServerTest.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <stdio.h>
#include <winsock2.h> #define SERVER_PORT 5208 //侦听端口 int _tmain(int argc, _TCHAR* argv[])
{
WORD wVersionRequested;
WSADATA wsaData;
int ret, nLeft, length;
SOCKET sListen, sServer; //侦听套接字,连接套接字
struct sockaddr_in saServer, saClient; //地址信息
char *ptr;//用于遍历信息的指针
//WinSock初始化
wVersionRequested=MAKEWORD(, ); //希望使用的WinSock DLL 的版本
ret=WSAStartup(wVersionRequested, &wsaData);
if(ret!=)
{
printf("WSAStartup() failed!\n");
//return 0;
}
//创建Socket,使用TCP协议
sListen=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sListen == INVALID_SOCKET)
{
WSACleanup();
printf("socket() faild!\n");
//return 0;
}
//构建本地地址信息
saServer.sin_family = AF_INET; //地址家族
saServer.sin_port = htons(SERVER_PORT); //注意转化为网络字节序
saServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //使用INADDR_ANY 指示任意地址 //绑定
ret = bind(sListen, (struct sockaddr *)&saServer, sizeof(saServer));
if (ret == SOCKET_ERROR)
{
printf("bind() faild! code:%d\n", WSAGetLastError());
closesocket(sListen); //关闭套接字
WSACleanup();
//return 0;
} //侦听连接请求
ret = listen(sListen, );
if (ret == SOCKET_ERROR)
{
printf("listen() faild! code:%d\n", WSAGetLastError());
closesocket(sListen); //关闭套接字
//return 0;
} printf("Waiting for client connecting!\n");
printf("Tips: Ctrl+c to quit!\n");
//阻塞等待接受客户端连接
while()//循环监听客户端,永远不停止,所以,在本项目中,我们没有心跳包。
{
length = sizeof(saClient);
sServer = accept(sListen, (struct sockaddr *)&saClient, &length);
if (sServer == INVALID_SOCKET)
{
printf("accept() faild! code:%d\n", WSAGetLastError());
closesocket(sListen); //关闭套接字
WSACleanup();
return ;
} char sendMessage[]="hello client"; //发送信息给客户端
send(sServer,sendMessage,strlen(sendMessage)+,); char receiveMessage[];
nLeft = sizeof(receiveMessage);
ptr = (char *)&receiveMessage;
while(nLeft>)
{
//接收数据
ret = recv(sServer, ptr, , );
if (ret == SOCKET_ERROR)
{
printf("recv() failed!\n");
return ;
}
if (ret == ) //客户端已经关闭连接
{
printf("Client has closed the connection\n");
break;
}
nLeft -= ret;
ptr += ret;
}
printf("receive message:%s\n", receiveMessage);//打印我们接收到的消息。 }
// closesocket(sListen);
// closesocket(sServer);
// WSACleanup();
return ;
}
第一步,加载套接字库,和客户端得加载套接字一样。
第二步,创建监听套接字,sListen=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);仍然使用的是socket函数。
第三步,绑定。使用bind函数,该函数的作用是将一个创建好的套接字绑定到本地的某个地址和端口上,该函数的原型为:
int bind(SOCKET s, const struct sockaddr FAR* name, int namelen);
第一个参数,指定要绑定的套接字;
第二个参数,指定该套接字的地址信息,这里即服务器的地址信息,它仍是指向struct sockaddr_in类型的结构体的指针。这个结构体和客户端调用connect函数之前构建服务器地址信息的一样。其中INADDR_ANY 是指示任意地址,因为服务器含有可能多个网卡,可能有多个IP地址,这边指选择一个任意可用的地址。
第三个参数,地址的信息的长度。
第四步,监听连接。使用listen函数,该函数是将指定的套接字设置为监听模式,如ret = listen(sListen, 5)。函数原型为
int listen(SOCKET s, int backlog);
第一个参数,是要设置为监听的套接字描述符。
第二个参数,是等待连接队列的最大的长度。注意了,设置这个值是为了设置等待连接队列的最大长度,而不是在一个端口上可以连接的最大数目。例如,设置为5,当有6个连接请求同时到达,前面5个连接请求会被放到等待请求连接队列中,然后服务器依次处理这些请求服务,但是第6个连接请求会被拒绝。
第五步,接受客户端的连接请求。使用accept函数接受客户端发送的连接请求,如sServer = accept(sListen, (struct sockaddr *)&saClient, &length);该函数的原型为
SOCKET accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
第一个参数,是一个已设为监听模式的socket的描述符。
第二个参数,是一个返回值,它指向一个struct sockaddr类型的结构体的变量,保存了发起连接的客户端得IP地址信息和端口信息。
第三个参数,也是一个返回值,指向整型的变量,保存了返回的地址信息的长度。
accept函数返回值是一个客户端和服务器连接的SOCKET类型的描述符,在服务器端标识着这个客户端。
第六、七步是发送和接收,分别使用send和recv函数,这里和客户端的一样,不再重复。
accept函数是放在一个死循环中的,一直监听客户的请求。当服务器关闭时要关闭所有的套接字,和释放资源。
【转/TCP协议编程】 基于TCP的Socket 编程的更多相关文章
- 网络编程(二)——TCP协议、基于tcp协议的套接字socket
TCP协议与基于tcp协议的套接字socket 一.TCP协议(流式协议) 1.可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的 ...
- 网络编程(二)--TCP协议、基于tcp协议的套接字socket
一.TCP协议(Transmission Control Protocol 传输控制协议) 1.可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会 ...
- Java 网络编程 -- 基于TCP 模拟多用户登录
Java TCP的基本操作参考前一篇:Java 网络编程 – 基于TCP实现文件上传 实现多用户操作之前先实现以下单用户操作,假设目前有一个用户: 账号:zs 密码:123 服务端: public c ...
- 【TCP协议】(3)---TCP粘包黏包
[TCP协议](3)---TCP粘包黏包 有关TCP协议之前写过两篇博客: 1.[TCP协议](1)---TCP协议详解 2.[TCP协议](2)---TCP三次握手和四次挥手 一.TCP粘包.拆包图 ...
- 《网络安全编程基础》之Socket编程
<网络安全编程基础>之Socket编程 我的代码 server.c // server.cpp : Defines the entry point for the console appl ...
- 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程
Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...
- 网络编程: 基于TCP协议的socket, 实现一对一, 一对多通信
TCP协议 面向连接 可靠的 面向字节流形式的 tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端 TCP协议编码流程: 服务器端: 客户端 实例化对 ...
- 基于C#的socket编程的TCP同步实现
该博客源著地址https://www.cnblogs.com/sunev/archive/2012/08/05/2604189.html 一.摘要 总结一下基于C#的TCP传输协议的涉及到的常用方法及 ...
- 基于TCP协议的项目架构之Socket流传输的实现
项目背景 某银行的影像平台由于使用时间长,服务器等配置原因,老影像系统满足不了现在日益增长的数据量的需求,所以急需要升级改造.传统的影像平台使用的是Oracle数据库和简单的架构来存储数据(视频.图 ...
- 基于C#的socket编程的TCP异步实现
一.摘要 本篇博文阐述基于TCP通信协议的异步实现. 二.实验平台 Visual Studio 2010 三.异步通信实现原理及常用方法 3.1 建立连接 在同步模式中,在服务器上使用Accept方法 ...
随机推荐
- 为什么源码中很多方法就一行throw new RuntimeException("Stub!")
在使用某些类的方法时,发现其内部就一行throw new RuntimeException("Stub!"),但是实际运行中并没有抛出该错误,该方法也并没有语法报错. 因此可能是系 ...
- Gson应用:从json格式简单字符串中获取value
import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; i ...
- SNAT端口转发配置
需求说明 在只有外网地址的机器上也能正常访问内网地址 配置过程 环境网络信息 网络名称 网络地址 外网 192.168.200.0/24 (网关:192.168.200.251) 内网 92.0.0. ...
- Docker:容器与主机时间不同步问题解决
在Docker容器运行后,可能会发现容器时间与宿主机时间不一致,一般会差8个小时.这样会造成在容器中运行的web程序打出的日志时间与实际时间不一致,如果web程序中有定时任务也会造成影响等,需要对宿主 ...
- 反射实现数据库增删改查DAO及DAOImpl源代码(一)
这是DAO层,第一次尝试,如有bug希望能即使反馈,我们共同进步.具体的用法和实现原理我会在前面几篇博客中补充更新.配置文件及项目目录结构会在下一篇中贴出! package com.javasm.su ...
- 洛谷P3966 [TJOI2013]单词(后缀自动机)
传送门 统计单词出现次数……为啥大家都是写AC自动机的嘞……明明后缀自动机也能做的说…… 统计出现次数这个就直接按长度排序然后做个dp就好,这是SAM的板子的要求啊,不提了 然后考虑怎么让所有串之间隔 ...
- [Xcode 实际操作]三、视图控制器-(8)在Storyboard中设置初始化视图控制器
目录:[Swift]Xcode实际操作 本文将演示如何设置故事板的初始视图控制器. 打开自动生成的默认故事板[Main.storyboard] 在打开的故事板中,系统已经生成了一个空白的视图控制器. ...
- [Xcode 实际操作]四、常用控件-(2)UIButton图片按钮的使用
目录:[Swift]Xcode实际操作 本文将演示图片按钮的使用 在项目导航区,打开视图控制器的代码文件[ViewController.swift] import UIKit class ViewCo ...
- ubuntu 14.04安装octave,及其使用
从库安装: sudo apt-add-repository ppa:octave/stable sudo apt-get update sudo apt-get install octave 使用:以 ...
- [Leetcode]008.String to Integer (atoi)
public class Solution { public int myAtoi(String str) { int index = 0, sign = 1, total = 0; //1. 边界条 ...